You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Cursor proxy being too slow compared to regular codex subscription #2300
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
Use cursor model as the codex backend
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.
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.
지금 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:1271onFirstOutput이 그 스탬프. 브릿지가 reasoning/heartbeat/lifecycle를 받아도 TTFT가 찍힘. 유저 글자 델타가 아닐 수 있음.
502가 stall이라고 단정하면 안 됨. src/stall-timeout.tsDEFAULT_STALL_TIMEOUT_SEC = 300. 43.7초는 기본 워치독이 아님. stallTimeoutSec를 안 알려줌. src/server/request-log.ts:851-877httpStatusForTerminalStatus가 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 어댑터를 옮기면 리베이스하지 말고 닫고 다시 짜라. 지금은 그 정도 아님.
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.
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.
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.
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.
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.
Activity
Ingwannu commentedon Aug 21, 2026
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
502after43,773 ms.firstOutputMs: 8,859means 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_timeoutunlessstallTimeoutSecwas overridden. This may overlap the residual Cursor continuation/resource-pressure work in #1527, or #2210 only if the terminal reason is actuallyupstream_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, andattempts;continuationMode,checkpointPresent,checkpointInvalidationReason, and serialized prompt/context size if shown;stallTimeoutSec(or confirm it is unset);cursor/<model>through OpenCodex;ocx debug provider onbefore 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; the502means the report should not be dismissed as normal model latency.lidge-jun commentedon Aug 21, 2026
리뷰 · 우선순위 61 / 80
지금
devHEADc0cbe494e에서 이 로그는 느린 정상 턴이 아님.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:1271onFirstOutput이 그 스탬프. 브릿지가 reasoning/heartbeat/lifecycle를 받아도 TTFT가 찍힘. 유저 글자 델타가 아닐 수 있음.502가 stall이라고 단정하면 안 됨.
src/stall-timeout.tsDEFAULT_STALL_TIMEOUT_SEC = 300. 43.7초는 기본 워치독이 아님.stallTimeoutSec를 안 알려줌.src/server/request-log.ts:851-877httpStatusForTerminalStatus가completed만 200. 나머지 incomplete/failed는 502.max_output_tokensincomplete만 200. stall/EOF/unknown incomplete가 전부 502로 보임.terminalIncompleteReason/failureClass/upstreamError가 이 샘플에 없음. 그래서 경계를 못 가름.Cursor 쪽 잔여.
src/adapters/cursor/live-transport.ts:264-267unansweredinteractionQuery→ 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 Alphax-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, CursorcontinuationMode/checkpointPresent/checkpointInvalidationReason를 붙여라.stallTimeoutSec값. 같은 프롬프트를 Cursor 직접 vscursor/<model>ocx.ocx debug provider on프레임. 첫 업스트림 이벤트가 글자 델타인지 reasoning/heartbeat인지. 그 전엔 타임아웃 숫자를 흔들지 말 것. #2210/#2156/#1527이랑 합치지 말 것. 스플릿이 Cursor 어댑터를 옮기면 리베이스하지 말고 닫고 다시 짜라. 지금은 그 정도 아님.이 댓글은 grok-bot이 작성했습니다
Taewan-P commentedon Aug 21, 2026
Here is the new I log that I tested:
Here is the full provider log with the
ocx debug provider on:Taewan-P commentedon Aug 21, 2026
Ingwannu commentedon Aug 21, 2026
The new evidence narrows this substantially.
The request row and provider trace are consistent with one failure chain: Cursor produced real
textDeltaframes, 2,233 output tokens, checkpoints,stepCompleted, andturnEndedin about 18.98 seconds, then the transport reportedCursor request was aborted. The request remained open until the ordinary 300-second stall window expired (313.138stotal 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
devalready has a narrow guard for this exact class in60526d7af(fix(cursor): do not re-label a completed turn as failed when the stream is aborted), and that commit is contained by thev2.28.0tag. On that code, an abort after a queueddonemust not emitturn-failedor 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:
The
/healthzresponse reports the version of the actually running proxy process;ocx --versioncan 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/healthzreports the expected version, and reproduce one request with provider debug enabled.If the running
/healthzversion 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 sameturnEnded -> aborted -> 300s stallsequence still occurs, I will treat it as a residual terminal-settlement race and add a regression around the real frame/abort ordering. I am keepingneeds-infoonly for that runtime-identity check; the report itself is now confirmed as a real failed turn.Taewan-P commentedon Aug 21, 2026
Another cursor log. This is completely unusable. The priority needs to be higher than this.
Taewan-P commentedon Aug 21, 2026
Here is the requested output:
Additional information: Just upgraded to 2.29.0, but still have the same issue.
Ingwannu commentedon Aug 21, 2026
Confirmed and implemented in Draft PR #2307 on current
dev@401c24f74.The new trace proves the terminal ordering: Cursor emits real text, checkpoints,
stepCompleted, andturnEnded, then a clean ConnectEND_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-infoand added the provider/streaming labels. The PR remains draft for normal maintainer review; I have not merged it.Ingwannu commentedon Aug 21, 2026
Draft PR #2307 is now fully green at exact head
56bff341a724233c114144df3c0b3cde3af49ef4, including the clean ConnectEND_STREAMplus 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.lidge-jun commentedon Aug 25, 2026
Fixed on
devby #2307, with the regression attests/cursor-eof-terminal.test.ts:195naming 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
turnEndedin ~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_STREAMnow 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.