Skip to content

Cursor proxy being too slow compared to regular codex subscription #2300

Closed

Description

@Taewan-P

Client or integration

Codex App

Area

Proxy and routing

Summary

I experience a extreme slow api while using cursor as the codex proxy. This is not fast mode btw. Compared to using the cursor client, the same model feels 2~3 times more slow in Codex.

Reproduction

  1. Use cursor model as the codex backend
  2. Try using it

Version

2.28.0

Operating system

MacOS 26.5.2

Provider and model

cursor/ any model

Logs or error output

I do see some log like this:


  "requestId": "ocx-mt338y13-15",
  "timestamp": 1787325152583,
  "model": "gpt-5.6-sol",
  "provider": "cursor",
  "admissionKind": "loopback",
  "inboundProtocol": "responses",
  "conversationId": "810343447cae6ba56e96fc7a5e9a3636",
  "requestedModel": "cursor/gpt-5.6-sol",
  "requestedEffort": "max",
  "resolvedModel": "gpt-5.6-sol",
  "status": 502,
  "durationMs": 43773,
  "firstOutputMs": 8859,


As you can see, first output was 8seconds later and it was killed after 43 seconds. However, nothing was printed on the Codex side. This is extremely slow.

Screenshots and supporting files

No response

Redacted configuration

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. added
    proxyHTTP proxy, routing, reverse-proxy / management auth
    on Aug 21, 2026
  2. Ingwannu commented on Aug 21, 2026

    @Ingwannu
    Owner

    Thanks for the concrete request row. I checked this against current dev@c0cbe494e.

    This sample is a real failed turn, not just evidence of a 2-3x steady-state slowdown: it ended as HTTP 502 after 43,773 ms. firstOutputMs: 8,859 means OpenCodex observed the first downstream SSE bytes; it does not guarantee that visible answer text reached the Codex UI, because reasoning/lifecycle events can establish TTFT without rendering user-visible text.

    The timing alone does not identify the failing boundary. The default bridge stall budget is 300 seconds, so a 43.7-second failure is not the default upstream_stall_timeout unless stallTimeoutSec was overridden. This may overlap the residual Cursor continuation/resource-pressure work in #1527, or #2210 only if the terminal reason is actually upstream_stall_timeout, but I do not want to classify or close it as a duplicate without the terminal fields.

    Please attach the redacted Request-details entry for ocx-mt338y13-15, especially:

    • upstreamError, failureClass, terminalReason / incomplete reason, and attempts;
    • Cursor continuationMode, checkpointPresent, checkpointInvalidationReason, and serialized prompt/context size if shown;
    • your configured stallTimeoutSec (or confirm it is unset);
    • one fixed model id and the same-prompt timing through Cursor directly versus cursor/<model> through OpenCodex;
    • the final provider-debug frames for this request (ocx debug provider on before one fresh reproduction), with credentials and account identifiers removed.

    Please also say whether the first upstream events were only reasoning/heartbeat/lifecycle events or contained a visible text delta. With those fields we can separate a rendering symptom from a checkpoint/full-replay failure, an upstream typed error, or a local timeout and put the fix in the correct layer.

    I am leaving this open as needs-info; the 502 means the report should not be dismissed as normal model latency.

  3. added
    needs-infoWaiting on reporter for a concrete spec or reproduction
    on Aug 21, 2026
  4. lidge-jun commented on Aug 21, 2026

    @lidge-jun
    Owner

    리뷰 · 우선순위 61 / 80

    지금 dev HEAD c0cbe494e에서 이 로그는 느린 정상 턴이 아님. status: 502, durationMs: 43773, firstOutputMs: 8859, admissionKind: loopback, inboundProtocol: responses, provider: cursor, requestedModel: cursor/gpt-5.6-sol, requestedEffort: max. Codex UI에 글자가 안 나온 거랑, 프록시가 첫 SSE 바이트를 본 거랑 다름. src/server/index.ts:1271 onFirstOutput이 그 스탬프. 브릿지가 reasoning/heartbeat/lifecycle를 받아도 TTFT가 찍힘. 유저 글자 델타가 아닐 수 있음.

    502가 stall이라고 단정하면 안 됨. src/stall-timeout.ts DEFAULT_STALL_TIMEOUT_SEC = 300. 43.7초는 기본 워치독이 아님. stallTimeoutSec를 안 알려줌. src/server/request-log.ts:851-877 httpStatusForTerminalStatus가 completed만 200. 나머지 incomplete/failed는 502. max_output_tokens incomplete만 200. stall/EOF/unknown incomplete가 전부 502로 보임. terminalIncompleteReason/failureClass/upstreamError가 이 샘플에 없음. 그래서 경계를 못 가름.

    Cursor 쪽 잔여. src/adapters/cursor/live-transport.ts:264-267 unanswered interactionQuery → heartbeat-only stall → 워치독 → 502. 그건 300초 창. 43초에 죽었다면 워치독이 아님. 어댑터 error 이벤트/translation_buffer_limit (src/bridge.ts:802-808) / Cursor 업스트림 끊김 / 체크포인트 풀 리플레이 실패 (#1527 잔여) 후보. #2210은 터미널 이유가 upstream_stall_timeout일 때만 겹침. 지금 필드는 없음. 중복으로 닫지 말 것. needs-info 유지. Ingwannu가 같은 로그로 그 질문을 이미 함. 맞음.

    리포터 버전 2.28.0. 현재 dev는 v2.29.0 태그 + Bun 1.4.0 + Ox Alpha x-preview-f-free + deepseek-v4-flash-vision-exp. Cursor 어댑터 핫패스는 그 사이 커밋이 있음. 같은 ocx-mt338y13-15를 2.28에서 재현한 거면 그 빌드 기준. dev에서 한 번 더 찍어야 함. 비전공자 유지. 설정 JSON이 빔. stallTimeoutSec 기본인지 오버라이드인지 모름.

    #2188 L1–L9 사이드카 이미 dev. x_search 넣지 말 것. Grok OAuth Chat 기본(#2255)/GUI 옵트인 Responses(#2266)/#2283이랑 다른 레인임. types.ts/config.ts 스플릿 안 씹힘. 스키마 이슈 아님. 프리뷰 배포 아님. #2292/#2293 윈도우 피커랑 안 겹침. 유저 보이는 502라 61. 구현 PR 지금 열지 말 것.

    해결방안: 이 이슈는 needs-info로 열어 둬라. Request-details에 upstreamError, failureClass, terminalIncompleteReason, attempts, Cursor continuationMode/checkpointPresent/checkpointInvalidationReason를 붙여라. stallTimeoutSec 값. 같은 프롬프트를 Cursor 직접 vs cursor/<model> ocx. ocx debug provider on 프레임. 첫 업스트림 이벤트가 글자 델타인지 reasoning/heartbeat인지. 그 전엔 타임아웃 숫자를 흔들지 말 것. #2210/#2156/#1527이랑 합치지 말 것. 스플릿이 Cursor 어댑터를 옮기면 리베이스하지 말고 닫고 다시 짜라. 지금은 그 정도 아님.

    이 댓글은 grok-bot이 작성했습니다

  5. Taewan-P commented on Aug 21, 2026

    @Taewan-P
    Author

    Here is the new I log that I tested:

    {
      "requestId": "ocx-mt349hxq-41",
      "timestamp": 1787326857998,
      "model": "gpt-5.6-sol",
      "provider": "cursor",
      "admissionKind": "loopback",
      "inboundProtocol": "responses",
      "conversationId": "cd00494b30e7bb633a13bfd94b3d69b0",
      "requestedModel": "cursor/gpt-5.6-sol",
      "requestedEffort": "medium",
      "resolvedModel": "gpt-5.6-sol",
      "status": 502,
      "durationMs": 313138,
      "firstOutputMs": 3960,
      "errorCode": "upstream_server_error",
      "terminalStatus": "incomplete",
      "closeReason": "terminal",
      "upstreamError": "Upstream stalled: no data for the stall-timeout window (upstream_stall_timeout)",
      "usageStatus": "unreported",
      "attempts": [
        {
          "ordinal": 1,
          "provider": "cursor",
          "model": "gpt-5.6-sol",
          "adapter": "cursor",
          "status": 502,
          "durationMs": 313081,
          "sendCount": 1,
          "recoveryKinds": [],
          "usageStatus": "unreported",
          "firstOutputMs": 3903,
          "errorCode": "upstream_server_error",
          "displayMetrics": {
            "tokPerSecond": {
              "kind": "unavailable",
              "reason": "usage_missing"
            },
            "cost": {
              "kind": "unavailable",
              "reason": "usage_missing"
            }
          }
        }
      ],
      "transportPhase": "terminal_sse",
      "terminalSource": "upstream",
      "routeDecision": {
        "version": 1,
        "decisionId": "ec15a45ca954",
        "createdAt": 1787326858031,
        "requestedModel": "cursor/gpt-5.6-sol",
        "routeKind": "explicit-provider",
        "requirements": [],
        "candidates": [
          {
            "provider": "cursor",
            "model": "gpt-5.6-sol",
            "eligible": true,
            "exclusions": []
          }
        ],
        "selected": {
          "candidateIndex": 0,
          "provider": "cursor",
          "model": "gpt-5.6-sol",
          "reason": "explicit-provider-namespace"
        }
      },
      "displayMetrics": {
        "tokPerSecond": {
          "kind": "unavailable",
          "reason": "usage_missing"
        },
        "cost": {
          "kind": "unavailable",
          "reason": "combo_attempt_unavailable"
        }
      }
    }
    

    Here is the full provider log with the ocx debug provider on:

  6. Taewan-P commented on Aug 21, 2026

    @Taewan-P
    Author
    [12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"textDelta"}[12:53:34 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"tokenDelta"}[12:53:35 AM] [ocx:cursor:frame] {"case":"kvServerMessage","kv":"setBlobArgs"}[12:53:35 AM] [ocx:cursor:frame] {"case":"kvServerMessage","kv":"setBlobArgs"}[12:53:35 AM] [ocx:cursor:frame] {"case":"conversationCheckpointUpdate","usedTokens":34856}[12:53:35 AM] [ocx:cursor:frame] {"case":"kvServerMessage","kv":"setBlobArgs"}[12:53:35 AM] [ocx:cursor:frame] {"case":"conversationCheckpointUpdate","usedTokens":36666}[12:53:35 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"stepCompleted"}[12:53:35 AM] [ocx:cursor:frame] {"case":"conversationCheckpointUpdate","usedTokens":36666}[12:53:35 AM] [ocx:cursor:frame] {"case":"interactionUpdate","update":"turnEnded"}[12:53:35 AM] [ocx:cursor:connect-end-stream] {"framesReceived":357,"elapsedMs":18973}[12:53:35 AM] [ocx:cursor:turn-failed] {"committed":true,"framesReceived":357,"outputTokens":2233,"contextTokens":36666,"firstFrameMs":588,"elapsedMs":18978,"classified":"Cursor upstream error","message":"Cursor upstream error: Cursor request was aborted"}[12:53:35 AM] [ocx:cursor:no-retry] {"attempt":0,"reason":"Cursor upstream error: Cursor request was aborted","emittedAny":true,"committed":true,"aborted":true}[12:53:35 AM] [ocx:cursor:stream-end] {"committed":true,"framesReceived":357,"expectedClose":false,"elapsedMs":18988}
    
  7. Ingwannu commented on Aug 21, 2026

    @Ingwannu
    Owner

    The new evidence narrows this substantially.

    The request row and provider trace are consistent with one failure chain: Cursor produced real textDelta frames, 2,233 output tokens, checkpoints, stepCompleted, and turnEnded in about 18.98 seconds, then the transport reported Cursor request was aborted. The request remained open until the ordinary 300-second stall window expired (313.138s total is approximately the 18.98-second Cursor turn plus that watchdog window). This is therefore not normal Cursor model latency and not a first-token problem; a completed-looking Cursor turn is being lost at the transport/bridge terminal boundary.

    Current dev already has a narrow guard for this exact class in 60526d7af (fix(cursor): do not re-label a completed turn as failed when the stream is aborted), and that commit is contained by the v2.28.0 tag. On that code, an abort after a queued done must not emit turn-failed or leave the bridge waiting for the stall watchdog. Your trace still shows that pre-fix outcome, so the first remaining question is runtime identity rather than another timeout change.

    Please run these against the same installation and paste the non-secret output:

    ocx --version
    ocx status
    curl -s http://127.0.0.1:10100/healthz
    

    The /healthz response reports the version of the actually running proxy process; ocx --version can report a newly installed CLI even if an older service process is still alive. Then restart the proxy once through the normal OCX path, confirm /healthz reports the expected version, and reproduce one request with provider debug enabled.

    If the running /healthz version was stale and the restart fixes it, this is an update/restart-service issue rather than a new Cursor adapter defect. If the running process is confirmed current and the same turnEnded -> aborted -> 300s stall sequence still occurs, I will treat it as a residual terminal-settlement race and add a regression around the real frame/abort ordering. I am keeping needs-info only for that runtime-identity check; the report itself is now confirmed as a real failed turn.

  8. Taewan-P commented on Aug 21, 2026

    @Taewan-P
    Author

    Another cursor log. This is completely unusable. The priority needs to be higher than this.

    {
      "requestId": "ocx-mt34z2qn-1",
      "timestamp": 1787328051359,
      "model": "gpt-5.6-sol",
      "provider": "cursor",
      "admissionKind": "loopback",
      "inboundProtocol": "responses",
      "conversationId": "cd00494b30e7bb633a13bfd94b3d69b0",
      "requestedModel": "cursor/gpt-5.6-sol",
      "requestedEffort": "medium",
      "tierOutcome": {
        "wireKind": null,
        "wireValue": null,
        "fastOutcome": "unknown",
        "confirmation": "unknown"
      },
      "resolvedModel": "gpt-5.6-sol",
      "status": 502,
      "durationMs": 328859,
      "firstOutputMs": 4836,
      "errorCode": "upstream_server_error",
      "terminalStatus": "incomplete",
      "closeReason": "terminal",
      "upstreamError": "Upstream stalled: no data for the stall-timeout window (upstream_stall_timeout)",
      "usageStatus": "unreported",
      "attempts": [
        {
          "ordinal": 1,
          "provider": "cursor",
          "model": "gpt-5.6-sol",
          "adapter": "cursor",
          "status": 502,
          "durationMs": 328811,
          "sendCount": 1,
          "recoveryKinds": [],
          "usageStatus": "unreported",
          "tierOutcome": {
            "wireKind": null,
            "wireValue": null,
            "fastOutcome": "unknown",
            "confirmation": "unknown"
          },
          "firstOutputMs": 4788,
          "errorCode": "upstream_server_error",
          "displayMetrics": {
            "tokPerSecond": {
              "kind": "unavailable",
              "reason": "usage_missing"
            },
            "cost": {
              "kind": "unavailable",
              "reason": "usage_missing"
            }
          }
        }
      ],
      "transportPhase": "terminal_sse",
      "terminalSource": "upstream",
      "routeDecision": {
        "version": 1,
        "decisionId": "ce6d1401e2e9",
        "createdAt": 1787328051403,
        "requestedModel": "cursor/gpt-5.6-sol",
        "routeKind": "explicit-provider",
        "requirements": [],
        "candidates": [
          {
            "provider": "cursor",
            "model": "gpt-5.6-sol",
            "eligible": true,
            "exclusions": []
          }
        ],
        "selected": {
          "candidateIndex": 0,
          "provider": "cursor",
          "model": "gpt-5.6-sol",
          "reason": "explicit-provider-namespace"
        }
      },
      "displayMetrics": {
        "tokPerSecond": {
          "kind": "unavailable",
          "reason": "usage_missing"
        },
        "cost": {
          "kind": "unavailable",
          "reason": "combo_attempt_unavailable"
        }
      }
    }
    
  9. Taewan-P commented on Aug 21, 2026

    @Taewan-P
    Author

    Here is the requested output:

    opencodex 2.29.0
    ✅ Proxy: running (PID 30645)
       Health: http://127.0.0.1:10100/healthz ok (live)
       Dashboard: http://localhost:10100/
       Config: /Users/taewanpark/.opencodex/config.json
       PID file: /Users/taewanpark/.opencodex/ocx.pid
       Runtime: /Users/taewanpark/.local/lib/node_modules/@bitkyc08/opencodex/node_modules/bun/bin/bun.exe
       Runtime source: bundled
       Default provider: openai
       Codex autostart: enabled
       Restart safety: protected by background service
       Service: installed and loaded (launchd; logs: /Users/taewanpark/.opencodex/service.log)
       Codex autostart shim is not installed.
       Codex runtime: /Users/[USER]/.bun/bin/codex
       Codex version: 0.146.0
       Codex source: configured
       Codex home: /Users/[USER]/.codex
       Catalog clamp: inactive
       OAuth logins:
         command-code ✗ not logged in
         xai        ✓ logged in (s***w@gmail.com)
         anthropic  ✗ not logged in
         kimi       ✗ not logged in
         nous       ✗ not logged in
         kiro       ✗ not logged in
         google-antigravity ✗ not logged in
         cursor     ✓ logged in
         github-copilot ✗ not logged in
       OAuth health: ok
    {"status":"ok","service":"opencodex","version":"2.29.0","uptime":528.0869349579999,"pid":30645,"port":10100,"restartCapability":"v1","providerReloadCapability":"v1"}%
    

    Additional information: Just upgraded to 2.29.0, but still have the same issue.

  10. added
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    streamingSSE, WebSocket, terminal stream frames
    and removed
    needs-infoWaiting on reporter for a concrete spec or reproduction
    on Aug 21, 2026
  11. Ingwannu commented on Aug 21, 2026

    @Ingwannu
    Owner

    Confirmed and implemented in Draft PR #2307 on current dev@401c24f74.

    The new trace proves the terminal ordering: Cursor emits real text, checkpoints, stepCompleted, and turnEnded, then a clean Connect END_STREAM; the HTTP body can remain open or later close through an abort-shaped error. OpenCodex logged that protocol terminal but did not settle the transport, so the already-completed turn stayed open until the ordinary 300-second stall watchdog.

    #2307 makes the clean Connect terminal authoritative. It preserves an existing turnEnded, otherwise finalizes once through the current fail-closed open-tool logic, releases the backlog lease, and settles without waiting for HTTP EOF. Error-bearing Connect terminals remain failures.

    Verification after rebasing to current dev: 61/61 focused Cursor/terminal/bridge tests, typecheck, privacy scan, and diff check pass. The held-open-body regression now completes before its 500 ms fallback. I removed needs-info and added the provider/streaming labels. The PR remains draft for normal maintainer review; I have not merged it.

  12. Ingwannu commented on Aug 21, 2026

    @Ingwannu
    Owner

    Draft PR #2307 is now fully green at exact head 56bff341a724233c114144df3c0b3cde3af49ef4, including the clean Connect END_STREAM plus immediate abort-shaped-close regression requested in the Grok/owner follow-up. The issue remains open pending independent maintainer approval; no self-merge was performed.

  13. lidge-jun commented on Aug 25, 2026

    @lidge-jun
    Owner

    Fixed on dev by #2307, with the regression at tests/cursor-eof-terminal.test.ts:195 naming this issue directly.

    Your trace is what made this diagnosable, and it pointed somewhere other than where the title suggests. Cursor produced 2,233 tokens and turnEnded in ~18.98s — the model was not slow. OpenCodex then waited ~300s for HTTP EOF before settling the turn. So the cost was a terminal-settlement race, not translation overhead or model latency.

    A clean Connect END_STREAM now settles the turn immediately (src/adapters/cursor/live-transport.ts:1154) instead of waiting for the HTTP body to close. Falsified by restoring the pre-fix behavior: Expected < 450ms, received 515ms; with current code the same case settles in 2.24ms.

    Two adjacent things landed that are worth knowing but do not explain your trace: #2210 added 30s/90s transport watchdogs for genuinely silent or heartbeat-only streams, and #2605 removed a synchronous SQLite scan from the request path (which could affect concurrent requests during a dashboard refresh). Neither produces a turnEnded → 300s stall.

    On the "2-3x slower" framing, honestly: that comparison did not isolate proxy overhead — there was no same-prompt, same-model, same-effort, same-context direct control. What your evidence does establish is a removed ~300s failure delay, which is a much bigger effect than any per-turn translation cost would be, and is almost certainly what you were feeling.

    If it still feels slow on current dev, a matched A/B would be genuinely useful now that the settlement race is gone — that would isolate real per-turn cost rather than a stall. Closing on the fixed defect; reopen with a matched measurement if the gap persists.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingproviderProvider adapters, OpenAI-compat presets, upstream API quirksproxyHTTP proxy, routing, reverse-proxy / management authstreamingSSE, WebSocket, terminal stream frames

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions