Skip to content

fix(cursor): do not re-label a completed turn as failed when the stream is aborted - #2118

Merged
lidge-jun merged 2 commits into
devfrom
fix/cursor-abort-teardown
Aug 19, 2026
Merged

fix(cursor): do not re-label a completed turn as failed when the stream is aborted#2118
lidge-jun merged 2 commits into
devfrom
fix/cursor-abort-teardown

Conversation

@lidge-jun

Copy link
Copy Markdown
Owner

Summary

Stacked on #2117#2116dev. Retarget down the chain as parents land.

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 to the consumer, still surfaced as turn-failed with expectedClose: 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-k3 premature completion at ~79-95k input — needs live acceptance on the checkpoint branch
  • claude-fable-5 429 asymmetry — may be unprovable while Connect does not expose cache_read_tokens
  • first-turn / restart / compaction full-replay into the 512 KiB envelope
  • request-shape parity with official Cursor (maxMode)

Hence Refs, not Closes.

Verification

bun test tests/cursor-cancel-provenance.test.ts tests/cursor-eof-terminal.test.ts \
         tests/cursor-adapter.test.ts tests/cursor-errors.test.ts     46 pass / 0 fail
bun x tsc --noEmit                                                    exit 0

Driven red. Removing the two emittedTerminal && isCursorAbortError guards 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 the if (failure) throw inside run(). A green suite after that first edit would have been a change that verified nothing.

Checklist

  • Focused tests pass
  • tsc --noEmit clean
  • Regression driven red before the fix
  • Guard test for the narrowing
  • Existing contract test unchanged and still passing
  • Repository CI (deferred while the merge train settles)

Refs #1527.

@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner August 19, 2026 12:53
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@github-actions github-actions Bot added the bug Something isn't working label Aug 19, 2026
@coderabbitai

coderabbitai Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (2)
  • ^dev$
  • ^preview$

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3e9f25e4-1963-4c07-b28b-5e80507bd70e

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 54 / 80

#2117#2116 위에 쌓인 세 번째 조각임. 커서 어댑터가 이미 끝난 턴을 abort 때문에 다시 turn-failed로 찍던 버그임. cancelCursorRun()expectedClose를 켜서, 평범한 완료 턴은 benign-close 경로를 못 탐. abort 리스너가 "Cursor request was aborted"로 실패 처리함. 이 에러는 중간 abort라서 원래 benign이 아님. 근데 터미널 프레임이 이미 나갔고 메시지도 컨슈머에 넘어간 뒤에 그 실패가 붙으면, 성공한 턴이 expectedClose: false로 죽음.

고친 곳은 src/adapters/cursor/live-transport.tsrun()if (failure)임. 처음엔 failAndClear를 건드렸는데 ablation 하니까 테스트가 하나도 안 움직임. 그 헬퍼는 이 실패 경로에 없음. 가드는 emittedTerminal && isCursorAbortError 둘 다일 때만 return임. 터미널만 보면 진짜 장애 의미가 바뀌고, abort만 보면 아무 것도 안 준 중간 abort를 삼킴. 두 번째 케이스 가드 테스트 있음. cursor.ts:183의 터미널 이후 benign cancel은 안 건드림. 기존 계약 테스트(터미널 후 캔슬이 raw cancel을 던짐)도 그대로 통과함.

#1527 전체는 이 PR이 아님. 남은 네 조각 있음. kimi 조기 완료, claude-fable 429, 512KiB 풀리플레이, official Cursor maxMode 패리티. 그래서 RefsCloses가 아님. #2054 이후 잔여 중 teardown 오분류만 자른 거임.

점수 54임. 재현 가능하고 테스트로 빨개지게 증명됨. 근데 스택 맨 위라 #2116 #2117 먼저임. 지금 dev 최우선 불은 아님. types 분할이랑 안 겹침. 닫으라는 소리 아님.

해결방안은 부모 두 개 넣고 리타겟한 다음 이 슬라이스만 머지하는 거임. #1527은 나머지 네 개 따로 이슈/PR로 쪼개 두는 게 맞음. 한 PR에 다 넣으면 또 87파일 사태 남.

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

jun added 2 commits August 19, 2026 23:26
…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.
@lidge-jun
lidge-jun force-pushed the fix/cursor-abort-teardown branch from 2c4e00e to b571284 Compare August 19, 2026 14:26
@lidge-jun
lidge-jun force-pushed the fix/tray-registry-encoding branch from f1f400f to 4cb3631 Compare August 19, 2026 14:26
@lidge-jun
lidge-jun changed the base branch from fix/tray-registry-encoding to dev August 19, 2026 14:53
@lidge-jun
lidge-jun merged commit 239cae9 into dev Aug 19, 2026
48 of 62 checks passed
@lidge-jun
lidge-jun deleted the fix/cursor-abort-teardown branch August 19, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant