Repository navigation
[Bug]: Cursor adapter large-context turns collapse or rate-limit while direct Cursor remains healthy #1527
Description
Bug report
Client or integration
Codex App / Cursor provider adapter
Area
Provider adapter / streaming / large-context continuation
Current status
Later reporter evidence supersedes the original framing of this issue.
The problem is not simply that kimi-k3 gives short answers or that claude-fable-5 receives ordinary upstream 429s. Direct controls through Cursor's own client show the same account and models completing large-context workloads normally, while the OpenCodex Cursor adapter path degrades sharply at comparable context sizes.
Composer routes remain out of scope unless separately reproduced.
This issue now tracks the OpenCodex-specific large-context Cursor path.
Observed behavior through OpenCodex
At large context sizes, routed Cursor turns can show one or both of these symptoms:
cursor/kimi-k3completes with only a few output tokens despite a large multi-step task;cursor/claude-fable-5and/or other Cursor routes hit repeated429 rate_limit_exceeded/ resource-limit failures at context sizes that remain usable through the official Cursor client.
The adapter also consistently logs completed streams as an abort-style teardown:
[ocx:cursor:turn-failed] ... "Cursor request was aborted" ... "committed":true
[ocx:cursor:stream-end] ... "expectedClose":false
That classification occurs even on turns whose committed output is otherwise usable.
Decisive control
The reporter repeated comparable large-context workloads through Cursor's own client with OpenCodex removed from the path.
Those controls produced long multi-step outputs, successful tool loops, and no equivalent rate-limit storm on the same account at similar or larger session sizes.
This rules out the earlier conclusion that the short output is simply normal model behavior.
Strongest current implementation hypothesis
The OpenCodex Cursor adapter appears to replay substantially more conversation state per turn than the official Cursor continuation path.
Observed differences include:
- official Cursor usage reports substantial
cache_read_tokensduring long sessions; - OpenCodex usage for these routed Cursor turns is estimated and does not expose equivalent cache-reuse evidence;
- the adapter serializes the conversation/history into
rootPromptMessagesJsonfor each turn; - large histories approach the adapter's bounded serialized prompt envelope;
- repeated full replay may increase upstream processing pressure and can plausibly explain the large-context degradation and rate-limit behavior.
This is the strongest current direction, but it should remain a hypothesis until a side-by-side request/continuation trace proves the exact upstream contract difference.
The constant abort/expectedClose:false teardown is separately actionable even if it is not the cause of the context collapse.
Required investigation
- Compare the official Cursor continuation/request shape with OpenCodex for the same large-context thread.
- Determine whether OpenCodex can reuse Cursor conversation/context caching or stable continuation state instead of replaying the full history each turn.
- Measure serialized prompt bytes and upstream request cost as the context grows.
- Determine whether the adapter is reducing an upstream output budget or otherwise changing request semantics at large context.
- Correct normal completed-stream teardown so it is not classified as
Cursor request was aborted/expectedClose:false. - Keep Composer out of scope unless independent evidence shows the same defect there.
Acceptance criteria
- A controlled large-context Cursor workload through OpenCodex no longer collapses to a few output tokens solely because the conversation grows.
- Comparable OpenCodex and direct-Cursor workloads do not show a large unexplained difference in rate-limit/resource pressure caused by avoidable full replay.
- The continuation/cache behavior is documented and regression-tested.
- A successfully completed Cursor stream is not classified as an abort during normal teardown.
- Existing committed output is not lost while fixing teardown classification.
- Small-context and Composer behavior do not regress.
Original evidence
The original OpenCodex session showed kimi-k3 turns around 79k-95k input collapsing to roughly 4-36 output tokens, while direct Cursor controls at comparable or larger context completed long coding tasks with thousands of output tokens.
The reporter also observed repeated Cursor-adapter 429/resource-limit failures at large context while direct Cursor on the same account remained healthy.
Related
Partial overlap exists with earlier Cursor/tool/provider issues, but this issue owns the large-context continuation/replay and teardown defect rather than generic Cursor provider compatibility.
Activity
github-actions commentedon Aug 12, 2026
Issue reopened
The report now contains the information required by the automated check. Thanks for updating it.
rennzhang commentedon Aug 12, 2026
Scope correction from reporter
After further user validation, Cursor Composer routes are considered OK on this setup.
The remaining problematic routes are:
cursor/kimi-k3cursor/claude-fable-5429 rate_limit_exceededPlease treat Composer (
composer-2.5,composer-2.5-fast) as out of scope for this report unless additional reproduction is provided.Still-relevant evidence (kimi-k3)
Still-relevant evidence (claude-fable-5)
429 rate_limit_exceeded: 12/15 requests in the same conversationocx-mspotuyh-24,ocx-mspotzzn-26,ocx-mspou4m5-27If maintainers prefer, this issue can be narrowed/renamed to focus only on kimi-k3 premature completion and claude-fable-5 rate limiting via Cursor adapter.
[-][Bug]: Cursor adapter returns HTTP 200 with premature completion on kimi-k3/composer routes; claude-fable-5 rate-limited[/-][+][Bug]: Cursor adapter issues on kimi-k3 and claude-fable-5 (premature completion / rate limit); composer routes OK[/+]Ingwannu commentedon Aug 12, 2026
Thanks for narrowing the scope. The 429s from claude-fable-5 are upstream quota/rate-limit evidence by themselves, not yet an OCX adapter defect. The kimi-k3 rows are suspicious, but a successful 200 with a short answer does not tell us whether Cursor emitted an early terminal event or OpenCodex truncated/translated one.\n\nPlease capture one failing turn at the terminal event boundary with secrets removed: the final upstream Cursor stream events (especially stop/finish reason and any usage/terminal event), the corresponding OCX downstream response.completed payload, and whether the upstream stream itself contains only those 4-19 output tokens. A small-context control using the same prompt/model would also help separate a model/context limit from adapter handling.\n\nI will keep this issue focused on kimi-k3 premature completion. We should track claude-fable-5 only if a 429 is mishandled (for example, not surfaced or not participating in configured failover), rather than treating the rate limit itself as an adapter bug.
rennzhang commentedon Aug 12, 2026
Thanks for the guidance — I captured the terminal event boundary evidence as requested, with secrets removed.
Repro method
Direct requests to the proxy (loopback,
model: cursor/kimi-k3, streaming), with provider debug capture enabled (ocx debug provider on), reading both the upstream Cursor stream frames and the downstreamresponse.completedpayload.1. Large-context turn (~56k input tokens) — the failing case
Upstream Cursor stream, final events in order:
interactionUpdate× N:thinkingDelta→thinkingCompleted→textDelta(one short segment) →tokenDelta→heartbeat→stepCompletedconversationCheckpointUpdateusedTokens: 55992interactionUpdateturnEndedconnect-end-stream(103 frames total)Downstream OCX
response.completed:{"type":"response.completed","response":{"id":"resp_...","status":"completed","model":"kimi-k3","usage":{"input_tokens":55883,"output_tokens":109,"total_tokens":55992,"input_tokens_details":{"cached_tokens":0},"output_tokens_details":{"reasoning_tokens":0}}}}Output text actually delivered: 9 characters (
完成。完成。完成。).Key answer to your question: the upstream stream itself contains only that short output. Cursor emitted one short
textDelta, thenstepCompleted+turnEndedon its own. OpenCodex did not truncate it — it translated everything the upstream sent and completed the response normally.2. Small-context control (same prompt style, ~15k input)
textDeltasequence →stepCompleted→turnEnded, 47–66 framesresponse.completed, usage{input_tokens:15127, output_tokens:30}, complete sentence delivered3. Additional finding — connection close is always classified as aborted
In both cases above, the adapter's stream teardown is logged as:
So every kimi-k3 turn (even fully-completed small ones) ends with the upstream close being classified as
aborted/expectedClose: false. Committed output is preserved (committed: true), so users only notice when the upstream output is short — which is exactly the large-context case.Conclusion
Cursor request was aborted,expectedClose: false) on the Cursor adapter for kimi-k3. It does not drop committed output, but it is wrong in both repros.Happy to narrow this issue to the teardown misclassification if that is actionable, or close it as upstream-confirmed otherwise.
rennzhang commentedon Aug 12, 2026
Correction — my previous "upstream behavior" conclusion was wrong. I ran a model-side control via a different client (Cursor agent CLI direct, no OpenCodex in the path), same input scale, and kimi-k3 completes long multi-step tasks normally.
Model-side control (Cursor agent CLI direct, bypassing OpenCodex)
Same kimi-k3, ~48k input tokens, a real multi-step coding task (write 3 files, run tests, fix, document):
calculator.py(116 lines),test_calculator.py(82 lines, 19 unittest cases),README.md(61 lines)So the model is fully capable of long tasks at this context size. The short outputs observed through the OpenCodex Cursor adapter (4–19 tokens at ~56–90k input, and my repro at 55883 in / 109 out / 9 chars text) do not reflect upstream model behavior — they point at the OpenCodex Cursor adapter path.
This also re-frames my earlier finding: every kimi-k3 turn through the adapter ends with
Cursor request was aborted/expectedClose: false(even fully-completed small turns). Combined with the model-side control above, that teardown misclassification looks like the actionable bug: the adapter may be ending the stream early (or mis-closing it) before the upstream finishes, then committing whatever few tokens arrived.Open to capturing adapter-side traces for a large-context turn with your instrumentation if that helps.
rennzhang commentedon Aug 12, 2026
Decisive control — same model, same ~85-90k context, different client path. The model is fine; the OpenCodex Cursor adapter path is where the short outputs come from.
Control: Cursor agent CLI direct (no OpenCodex), ~85k+ context
Same kimi-k3, same multi-step task (calculator project), context inflated to ~85k tokens with filler + real task, via
cursor-agent --model kimi-k3:So at the same context scale where the OpenCodex path produced 4–19 token turns, the model directly through Cursor's own client produces ~10k output tokens and finishes a long task. The "say one sentence and stop" behavior is specific to the OpenCodex Cursor adapter path, not the model or Cursor upstream.
What the OpenCodex path shows at the same scale
From the reporter conversation (
a0327fc163808b109032ba680d3009c5), kimi-k3 via OpenCodex:And every kimi-k3 turn through the adapter ends with:
even when output is complete.
Open question for maintainers
The adapter consistently classifies the upstream close as
aborted/expectedClose: falseon every kimi-k3 turn. Given the model demonstrably produces ~10k tokens directly at the same context size, please investigate the adapter path for: (1) early teardown/abort on large-context kimi-k3 turns, and (2) anything that would shrink per-turn output budget vs the direct client. Happy to capture a side-by-side wire trace of the same 90k request through both paths if that isolates it faster.rennzhang commentedon Aug 12, 2026
Final root-cause update, consolidating the previous three comments. The bug is in the OpenCodex Cursor adapter's large-context handling, not the model or Cursor upstream.
What the evidence shows
Same account, same models (kimi-k3 and claude-fable-5), same multi-step tasks:
cache_read_tokensup to 1.5M per turn (3.7M total)429 rate_limit_exceeded/Cursor Connect error resource limit exceeded; usage isestimated: truewith no cache fields at allTwo concrete adapter-side observations:
cache_read_tokensdominates every turn). Through OpenCodex, usage is estimated and cache counters are absent — the requests do not get cached-prompt treatment. In the source, every turn re-serializes the full history asrootPromptMessagesJsonblobs (capped at 512 KB, and 120k-token contexts sit right at that edge), i.e. full-price reprocessing each request instead of an incremental continuation.Cursor request was aborted/expectedClose: falseeven when output is complete (fable also shows this at 89k+, but its per-turn output stays usable).Suggested focus
Please verify whether the Cursor adapter can leverage Cursor's conversation/context cache for large-context turns (stable root prompt + incremental turns instead of full replay). The 429 storm at 89k–120k through OpenCodex, versus zero rate-limit events at the same account with the same context scale through the official client, points there.
Early "upstream behavior" framing from my first comment was wrong and was retracted in the follow-ups; the model-side controls above are the corrected baseline.
[-][Bug]: Cursor adapter issues on kimi-k3 and claude-fable-5 (premature completion / rate limit); composer routes OK[/-][+][Bug]: Cursor adapter large-context turns collapse or rate-limit while direct Cursor remains healthy[/+]25 remaining items
lidge-jun commentedon Sep 2, 2026
Re-checked this against the current
devtree. The defects this report originally described have since been fixed, so the remaining question is narrower than the title suggests.Already addressed:
src/adapters/cursor/request-builder.ts:438), and final replay envelopes are bounded (src/adapters/cursor/protobuf-request.ts:1580).src/adapters/cursor/transport-retry.ts:20), and a post-terminal abort no longer reclassifies a completed turn (src/adapters/cursor/live-transport.ts:716).max_output_tokens: it is not being lowered, because it has no wire field at all on this path (src/adapters/cursor/types.ts:12,src/adapters/cursor/gen/agent_pb.ts:2736).What is left: a cold full replay after a missing or expired checkpoint, a compaction, or a lineage change. That would explain the symptom, but only a live failing turn can establish it, and guessing would mean changing checkpoint logic on a hunch.
If you can still reproduce, the capture that would settle it: run the same large-context workload through direct Cursor and through OpenCodex, close in time and on the same account, and attach the OpenCodex
run-requestfieldscontinuationMode,rootBlobs,rootBytes,unmeasuredRoots(redact anything sensitive) plus the direct client'scache_read_tokens. If a valid continuation is full-replaying, the fix is confined toresolveCursorCheckpoint()atsrc/adapters/cursor/request-builder.ts:438-516.Leaving open with
needs-inforather than closing, since the underlying scenario is plausible and only the evidence is missing.lidge-jun commentedon Sep 3, 2026
@rennzhang — following up during a backlog review. This is not closed; it needs one piece of information from you to move.
Where this stands. The Cursor adapter has changed substantially since this was filed:
src/adapters/cursor/request-builder.tsandsrc/adapters/cursor/transport-retry.tswere reworked in c378435 and 6ec79e4, which addressed large-context request construction and retry behavior.What would unblock it. Please add a current reproduction on 2.41.0 or later — the model, approximate context size, and the upstream status/body you observe.
If that arrives, this gets picked up in the next triage pass. Without it, the report cannot be distinguished from configurations that already work, so it will keep sitting here rather than being wrong on purpose.
rennzhang commentedon Sep 4, 2026
Tested on 2.41.0. Sample is small so far — 5 turns on a real Codex thread (1 kimi + 4 fable) plus a 9-follow-up loopback series — but the original short-output collapse didn't reproduce in any of them. Thanks for the Cursor adapter work (checkpoint continuation especially):
cursor/kimi-k3andcursor/claude-fable-5are now usable for day-to-day work inside Codex.Details, same Cursor account as the original report:
/v1/responsesgrowth series, 9 follow-ups (ocx debug provider on): kimi-k3 context grown to 107k input, running oncontinuationMode=checkpointfrom the 2nd follow-up (rootBytes 94k→170k). Every turn endedexpectedClose: truewith normal-length output — no 4–19 token collapse.Separate observation: cache never hits through the proxy. Same account, same ~409KB prompt:
cursor-agent --model kimi-k3-max:cacheReadTokens=489972cached_tokens=0(usage markedestimated); the real Codex thread also showscached_input_tokens=0on every Cursor-routed turnFlagging it since it affects cost/latency on exactly these large-context threads. Happy to provide more logs (
run-requestcontinuationMode/rootBlobs/rootBytes, or a matched direct-vs-proxy pair) if useful.lidge-jun commentedon Sep 4, 2026
@rennzhang — that is the confirmation this issue was waiting for, so I am closing it as resolved.
The reported defect was the large-context collapse:
cursor/kimi-k3returning a handful of tokens on turns that should have produced real output, andclaude-fable-5hitting repeated 429s at context sizes that stayed healthy through Cursor's own client. Your 2.41.0 results are the inverse of that on the same account — 99k–157k input per turn completing normally, kimi-k3 at 153k input returning 1985 tokens across 4 tool loops, and the loopback series running oncontinuationMode=checkpointfrom the second follow-up with every turn endingexpectedClose: true. Those are exactly the turns that used to collapse.Your sample is small and you said so, which I appreciate. If the collapse returns on a later build, reopen this rather than filing fresh — the 18 comments of history here are worth keeping attached.
The cache observation is a different bug and should not ride along on a closed issue.
cacheReadTokens=489972direct versuscached_tokens=0through the proxy on the same ~409KB prompt is a real cost and latency problem, and it is the same shape as #3433, where consecutive zero cache hits were reported on a different route. There is a concrete finding on that one already: the Chat Completions bridge forwards only headers the caller already sent and never synthesizes asession_idfrom the body cache key, while the Claude bridge does exactly that synthesis and its code comment records that a body-onlyprompt_cache_keystill producedcached_tokens: 0. Cursor-routed turns showingcached_input_tokens=0on every turn fits that pattern.Please add your direct-versus-proxy pair to #3433 — the matched
cacheReadTokensandcached_tokensnumbers plus therun-requestcontinuationMode/rootBytescontext. A measured pair from a second provider is genuinely useful there, because it separates a Cursor-specific problem from a shared bridge one.Thanks for coming back with real numbers instead of leaving it open indefinitely.
fix(cursor): bound the assembled external replay envelope (lidge-jun#…
fix(cursor): keep checkpoint-suffix history intact through pruning an…
docs(devlog): scan the priority-70 backlog for round 2 and lock the r…
docs(devlog): record the 2026-08-31 non-priority-70 bug triage round (l…
fix(cursor): do not re-label a completed turn as failed when the stre…
fix(cursor): reuse conversation checkpoints for incremental continuat…
fix(cursor): bound the assembled external replay envelope (lidge-jun#…
fix(cursor): keep checkpoint-suffix history intact through pruning an…
docs(devlog): scan the priority-70 backlog for round 2 and lock the r…
docs(devlog): record the 2026-08-31 non-priority-70 bug triage round (l…