fix(cursor): do not re-label a completed turn as failed when the stream is aborted - #2118
Conversation
|
✅ Deterministic PR hygiene checks passed. |
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. 🗂️ Base branches to auto review (2)
Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 346eaa80d0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // turn that is then torn down still surfaced as `turn-failed` with | ||
| // `expectedClose:false`. Only `cancelCursorRun()` sets `expectedClose`, so a normal | ||
| // completion never qualified for the branch above. | ||
| if (this.emittedTerminal && isCursorAbortError(failure)) return; |
There was a problem hiding this comment.
Preserve the terminal at the adapter abort boundary
When the real AbortSignal fires after done is queued but before the iterator consumer handles it, returning here does not preserve completion: src/adapters/cursor.ts:130-133 sees the aborted signal and replaces the yielded done message with Cursor turn was aborted. The new test misses this because it injects an abort-shaped Error without aborting an AbortController. Add an adapter-level regression using an actually aborted signal and either allow an already-emitted terminal through the adapter's abort gate or track terminal delivery beyond the transport queue.
AGENTS.md reference: src/AGENTS.md:L17-L19
Useful? React with 👍 / 👎.
리뷰 · 우선순위 54 / 80#2117→#2116 위에 쌓인 세 번째 조각임. 커서 어댑터가 이미 끝난 턴을 abort 때문에 다시 고친 곳은 #1527 전체는 이 PR이 아님. 남은 네 조각 있음. kimi 조기 완료, claude-fable 429, 512KiB 풀리플레이, official Cursor 점수 54임. 재현 가능하고 테스트로 빨개지게 증명됨. 근데 스택 맨 위라 #2116 #2117 먼저임. 지금 해결방안은 부모 두 개 넣고 리타겟한 다음 이 슬라이스만 머지하는 거임. #1527은 나머지 네 개 따로 이슈/PR로 쪼개 두는 게 맞음. 한 PR에 다 넣으면 또 87파일 사태 남. 이 댓글은 grok-bot이 작성했습니다 |
e3b0637 to
f1f400f
Compare
346eaa8 to
2c4e00e
Compare
…am is aborted Only cancelCursorRun() sets expectedClose, so an ordinary completed turn never qualified for the benign-close path. The abort listener then failed the turn with 'Cursor request was aborted' — which is deliberately NOT a benign cancel, since a mid-turn abort is a real failure — so a turn whose terminal frame had already been emitted and whose messages had already been yielded still surfaced as turn-failed with expectedClose:false. Return instead of throwing when a terminal frame was already emitted AND the failure is an abort. Both halves matter: post-terminal alone would change what the adapter sees for genuine faults, and abort alone would swallow a real mid-turn abort where nothing was delivered. Deliberately narrow. A benign cancel after a terminal is already swallowed one layer up (cursor.ts:183), and the existing contract test that pins 'the transport still throws the raw cancel after a terminal' keeps passing — this does not widen that path. Refs #1527. This is the teardown-misclassification slice only; the kimi-k3 collapse and the 429 asymmetry are separate and need live acceptance work that cannot start until #2054 lands.
2c4e00e to
b571284
Compare
f1f400f to
4cb3631
Compare
Summary
Stacked on #2117 → #2116 →
dev. Retarget down the chain as parents land.Only
cancelCursorRun()setsexpectedClose, so an ordinary completed turn never qualified for the benign-close path. The abort listener then failed the turn with"Cursor request was aborted"— which is deliberately not a benign cancel, since a mid-turn abort is a real failure — so a turn whose terminal frame had already been emitted, and whose messages had already been yielded to the consumer, still surfaced asturn-failedwithexpectedClose: false.This returns instead of throwing when a terminal frame was already emitted and the failure is an abort.
Both halves of that condition matter. Post-terminal alone would change what the adapter sees for genuine faults; abort alone would swallow a real mid-turn abort where nothing was delivered. There is a guard test for the second case.
What this does not touch
A benign cancel after a terminal is already swallowed one layer up (
cursor.ts:183), and the existing contract test — "a cancel after a terminal was already emitted does not add a second one", which pins that the transport still throws the raw cancel — keeps passing unchanged. This deliberately does not widen that path.Scope note
This is the teardown-misclassification slice of #1527 only. The issue as filed has five residual parts after #2054; the other four are not this PR:
kimi-k3premature completion at ~79-95k input — needs live acceptance on the checkpoint branchclaude-fable-5429 asymmetry — may be unprovable while Connect does not exposecache_read_tokensmaxMode)Hence
Refs, notCloses.Verification
Driven red. Removing the two
emittedTerminal && isCursorAbortErrorguards fails exactly the new post-terminal-abort test (7 pass / 1 fail) and nothing else.Worth recording: my first attempt patched
failAndClear, and the ablation showed no test moved at all — that helper is not on the path the injected failure takes. The real site is theif (failure)throw insiderun(). A green suite after that first edit would have been a change that verified nothing.Checklist
tsc --noEmitcleanRefs #1527.