Skip to content

[Bug]: Cursor adapter large-context turns collapse or rate-limit while direct Cursor remains healthy #1527

Closed

Description

@rennzhang

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-k3 completes with only a few output tokens despite a large multi-step task;
  • cursor/claude-fable-5 and/or other Cursor routes hit repeated 429 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_tokens during 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 rootPromptMessagesJson for 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

  1. Compare the official Cursor continuation/request shape with OpenCodex for the same large-context thread.
  2. Determine whether OpenCodex can reuse Cursor conversation/context caching or stable continuation state instead of replaying the full history each turn.
  3. Measure serialized prompt bytes and upstream request cost as the context grows.
  4. Determine whether the adapter is reducing an upstream output budget or otherwise changing request semantics at large context.
  5. Correct normal completed-stream teardown so it is not classified as Cursor request was aborted / expectedClose:false.
  6. 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

  1. github-actions commented on Aug 12, 2026

    @github-actions
    Contributor

    Issue reopened

    The report now contains the information required by the automated check. Thanks for updating it.

  2. rennzhang commented on Aug 12, 2026

    @rennzhang
    Author

    Scope correction from reporter

    After further user validation, Cursor Composer routes are considered OK on this setup.

    The remaining problematic routes are:

    1. cursor/kimi-k3

      • Symptom: HTTP 200 but turn ends after a very short final output (often only a few tokens) on large Codex contexts
      • User must repeatedly send "continue" to keep work moving
    2. cursor/claude-fable-5

      • Symptom: mostly unusable due to repeated 429 rate_limit_exceeded

    Please 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)

    requestId model status input output durationMs
    ocx-mspp4i2c-4b kimi-k3 200 90524 4 7346
    ocx-msppduwv-6k kimi-k3 200 91887 5 7639
    ocx-msppg7p8-74 kimi-k3 200 93200 7 7839
    ocx-mspp4c20-49 kimi-k3 200 67845 19 7607
    ocx-mspozus8-3m kimi-k3 200 79207 12 8047

    Still-relevant evidence (claude-fable-5)

    • 429 rate_limit_exceeded: 12/15 requests in the same conversation
    • Example requestIds: ocx-mspotuyh-24, ocx-mspotzzn-26, ocx-mspou4m5-27

    If 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.

  3. changed the title [-][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[/+] on Aug 12, 2026
  4. Ingwannu commented on Aug 12, 2026

    @Ingwannu
    Owner

    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.

  5. rennzhang commented on Aug 12, 2026

    @rennzhang
    Author

    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 downstream response.completed payload.

    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 → stepCompleted
    • conversationCheckpointUpdate usedTokens: 55992
    • interactionUpdate turnEnded
    • connect-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, then stepCompleted + turnEnded on 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)

    • Upstream: full textDelta sequence → stepCompleted → turnEnded, 47–66 frames
    • Downstream: response.completed, usage {input_tokens:15127, output_tokens:30}, complete sentence delivered

    3. Additional finding — connection close is always classified as aborted

    In both cases above, the adapter's stream teardown is logged as:

    [ocx:cursor:turn-failed] {"committed":true,...,"classified":"Cursor upstream error","message":"Cursor upstream error: Cursor request was aborted"}
    [ocx:cursor:stream-end] {"committed":true,...,"expectedClose":false}
    

    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

    • The "say one sentence and stop" behavior on large contexts is upstream behavior: kimi-k3 ends its turn after a few tokens at ~56k context. OpenCodex reflects it faithfully (200 + completed, no retry since output was already emitted).
    • There is a separate, constant teardown misclassification (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.

  6. rennzhang commented on Aug 12, 2026

    @rennzhang
    Author

    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):

    • Duration: 149s, output tokens: 9922 (input 48044, cache read 273136)
    • Completed: calculator.py (116 lines), test_calculator.py (82 lines, 19 unittest cases), README.md (61 lines)
    • Multi-turn tool loop ran: list dir → write files → attempt test runs → summarize
    • I independently re-ran the tests locally: 19/19 pass

    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.

  7. rennzhang commented on Aug 12, 2026

    @rennzhang
    Author

    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:

    • Usage: input 156355 / output 9868 tokens, cache read 1155456, duration 176.7s
    • Completed: 3 files, 19 unittest cases, multiple tool-loop turns (8 shell attempts etc.)
    • I re-ran the tests locally: 19/19 pass

    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:

    input tokens output tokens notes
    55k–70k 102–2208 (mixed, mostly OK) degradable but works
    79k–95k 4–36 (14 of 17 rows) turns collapse
    95811 5 last observed

    And every kimi-k3 turn through the adapter ends with:

    [ocx:cursor:turn-failed] {"classified":"Cursor upstream error","message":"Cursor request was aborted", "committed":true, ...}
    [ocx:cursor:stream-end] {"expectedClose":false, ...}
    

    even when output is complete.

    Open question for maintainers

    The adapter consistently classifies the upstream close as aborted / expectedClose: false on 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.

  8. rennzhang commented on Aug 12, 2026

    @rennzhang
    Author

    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:

    path context result
    Cursor agent CLI direct up to ~150k session 0 rate-limit errors across 10 stacked tasks (~95k output tokens); usage reports cache_read_tokens up to 1.5M per turn (3.7M total)
    OpenCodex Cursor adapter 89k–120k repeated 429 rate_limit_exceeded / Cursor Connect error resource limit exceeded; usage is estimated: true with no cache fields at all

    Two concrete adapter-side observations:

    1. No cache reuse. The direct client clearly hits Cursor's server-side context cache (cache_read_tokens dominates 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 as rootPromptMessagesJson blobs (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.
    2. Constant teardown misclassification. Every kimi-k3 turn through the adapter ends with Cursor request was aborted / expectedClose: false even 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.

  9. changed the title [-][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[/+] on Aug 12, 2026
  10. added
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    on Aug 15, 2026
  11. 25 remaining items

  12. lidge-jun commented on Sep 2, 2026

    @lidge-jun
    Owner

    Re-checked this against the current dev tree. The defects this report originally described have since been fixed, so the remaining question is narrower than the title suggests.

    Already addressed:

    • Full-history replay: checkpoints are reused when valid (src/adapters/cursor/request-builder.ts:438), and final replay envelopes are bounded (src/adapters/cursor/protobuf-request.ts:1580).
    • Retry amplification: rate limits are explicitly non-retryable (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-request fields continuationMode, rootBlobs, rootBytes, unmeasuredRoots (redact anything sensitive) plus the direct client's cache_read_tokens. If a valid continuation is full-replaying, the fix is confined to resolveCursorCheckpoint() at src/adapters/cursor/request-builder.ts:438-516.

    Leaving open with needs-info rather than closing, since the underlying scenario is plausible and only the evidence is missing.

  13. lidge-jun commented on Sep 3, 2026

    @lidge-jun
    Owner

    @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.ts and src/adapters/cursor/transport-retry.ts were 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.

  14. rennzhang commented on Sep 4, 2026

    @rennzhang
    Author

    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-k3 and cursor/claude-fable-5 are now usable for day-to-day work inside Codex.

    Details, same Cursor account as the original report:

    • Real long-lived Codex thread, 5 turns after upgrading: 99k–157k input tokens per turn, all completed normally. kimi-k3 at 153k input returned 1985 output tokens with 4 tool loops; fable at 105k input returned 3246 tokens. These were previously the turns that collapsed after a few tokens.
    • Loopback /v1/responses growth series, 9 follow-ups (ocx debug provider on): kimi-k3 context grown to 107k input, running on continuationMode=checkpoint from the 2nd follow-up (rootBytes 94k→170k). Every turn ended expectedClose: true with normal-length output — no 4–19 token collapse.

    Separate observation: cache never hits through the proxy. Same account, same ~409KB prompt:

    • direct cursor-agent --model kimi-k3-max: cacheReadTokens=489972
    • via OpenCodex: cached_tokens=0 (usage marked estimated); the real Codex thread also shows cached_input_tokens=0 on every Cursor-routed turn

    Flagging it since it affects cost/latency on exactly these large-context threads. Happy to provide more logs (run-request continuationMode / rootBlobs / rootBytes, or a matched direct-vs-proxy pair) if useful.

  15. lidge-jun commented on Sep 4, 2026

    @lidge-jun
    Owner

    @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-k3 returning a handful of tokens on turns that should have produced real output, and claude-fable-5 hitting 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 on continuationMode=checkpoint from the second follow-up with every turn ending expectedClose: 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=489972 direct versus cached_tokens=0 through 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 a session_id from the body cache key, while the Claude bridge does exactly that synthesis and its code comment records that a body-only prompt_cache_key still produced cached_tokens: 0. Cursor-routed turns showing cached_input_tokens=0 on every turn fits that pattern.

    Please add your direct-versus-proxy pair to #3433 — the matched cacheReadTokens and cached_tokens numbers plus the run-request continuationMode/rootBytes context. 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.

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 workingneeds-infoWaiting on reporter for a concrete spec or reproductionproviderProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reportsstreamingSSE, WebSocket, terminal stream framestoolstool_calls, MCP, web-search / sidecar tools

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions