Skip to content

fix(cursor): close HTTP/2 after turnEnded so a held-open response cannot stall the turn - #2321

Merged
lidge-jun merged 1 commit into
devfrom
codex/senpi-cursor-t02
Aug 22, 2026
Merged

fix(cursor): close HTTP/2 after turnEnded so a held-open response cannot stall the turn#2321
lidge-jun merged 1 commit into
devfrom
codex/senpi-cursor-t02

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • T03 (senpi #1062): after the server sends turnEnded, the application turn is complete. A server that keeps the HTTP/2 stream open past this point cannot hold the turn hostage until a 300s bridge idle timeout.
  • Close our side after a 500ms grace so trailing frames (late usage, checkpoint) still land before we release the socket.
  • The client-tool suspend path (live-transport.ts:203-206) intentionally ends without waiting for turnEnded and is not affected.

Stacked on #2320 (T01 error classification).

Verification

  • bun test tests/cursor-live-transport.test.ts tests/cursor-protobuf-events.test.ts tests/cursor-errors.test.ts — 78 pass / 0 fail
  • bun run typecheck — exit 0
  • No regression: the existing client-tool suspend path and EOF-synthesis path are untouched.

Checklist

  • Scope stays focused and avoids unrelated cleanup.
  • Docs or release notes were updated when needed.
  • Security-sensitive changes were reviewed for secrets, auth, and unsafe defaults.

Summary by CodeRabbit

  • Bug Fixes
    • Improved connection handling after a turn ends.
    • Added a brief grace period to allow final updates to be received before the connection closes.
    • Prevented unintended connection closure when a run has been intentionally cancelled.

@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner August 21, 2026 23:45
@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 9013f86e-1177-413c-974d-1d2593cdd451

📥 Commits

Reviewing files that changed from the base of the PR and between b08ea71 and 90dd314.

📒 Files selected for processing (1)
  • src/adapters/cursor/live-transport.ts

📝 Walkthrough

Walkthrough

The Cursor live transport now waits 500 ms after turnEnded before closing. Normal closure cancels the pending timer. Delayed closure handles expected cancellation, stops related timers, and closes or destroys the active connection.

Changes

Cursor transport closure

Layer / File(s) Summary
Closure timer state and cleanup
src/adapters/cursor/live-transport.ts
The transport defines a 500 ms grace period, stores the pending close timer, and clears it during normal closure.
Turn-ended delayed close flow
src/adapters/cursor/live-transport.ts
handleServerMessage schedules closure after mapping turnEnded. closeAfterTurnEnded handles cancellation, stops timers, logs diagnostics, and closes or destroys the connection.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: ingwannu, luvs01, yansigit

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/senpi-cursor-t02

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.

@github-actions github-actions Bot added the intake: hygiene-blocked Deterministic PR hygiene checks failed label Aug 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Deterministic hygiene checks failed.

  • missing_regression_test — Behavior changed under src/ or gui/src/ without a test change. Add focused coverage or obtain test-exception-approved.

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

github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

⏳ DRAFT

  • hygiene: missing_regression_test.

What to do

  • Fix missing_regression_test — Behavior changed under src/ or gui/src/ without a test change. Add focused coverage or obtain test-exception-approved.

Automatic draft conversion failed (token cannot change draft status). Please convert this pull request to a draft manually. The required enforce-target check will keep failing until every issue above is resolved.

@github-actions
github-actions Bot marked this pull request as draft August 21, 2026 23:46

@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: 4f2a37af17

ℹ️ 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".

Comment thread src/adapters/cursor/live-transport.ts Outdated
if (this.turnEndedCloseTimer) return;
this.turnEndedCloseTimer = setTimeout(() => {
this.turnEndedCloseTimer = undefined;
if (this.expectedClose || this.emittedTerminal) 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.

P1 Badge Allow the turn-ended timer to close the stream

When a turnEnded frame is decoded, mapCursorProtobufServerMessage() synchronously returns either done or the fail-closed error, and the local push() sets this.emittedTerminal = true before this 500 ms callback can run. This guard therefore always returns for the frame that armed the timer, so an upstream that holds HTTP/2 or HTTP/1.1 open continues to pin run() until the external stall timeout—the exact hang this change is intended to fix. Gate only an already-requested close (or track this timer with a dedicated flag), and cover the held-open-server case with a focused transport test.

AGENTS.md reference: src/AGENTS.md:L19-L19

Useful? React with 👍 / 👎.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 61 / 80

지금 dev HEAD 3a3f556e1. 이 PR 베이스는 dev가 아님. codex/senpi-cursor-t01 = #2320. 드래프트. 파일 1. +44/-0. gh api pulls/2321/files: src/adapters/cursor/live-transport.ts만. senpi T03 / senpi #1062. turnEnded 이후 HTTP/2를 우리가 닫아서 300s 브릿지 스톨에 안 묶이게. 하이진 실패 missing_regression_test. 게이트가 드래프트로 잠금. 테스트 파일 0. 스플릿 PR 아님.

현재 devturnEnded를 매퍼만 탐. src/adapters/cursor/protobuf-events.ts:1327-1328finalizeTurnEvents. done + usage. 트랜스포트는 Connect EOF를 기다림. EOF가 안 오면 src/stall-timeout.ts 기본 300s. 클라이언트 툴 경로는 live-transport.ts:203-206turnEnded 없이 일부러 끝냄. :1147-1150은 EOF + 어시스턴트 텍스트면 done 합성. 이 PR은 TURN_ENDED_CLOSE_GRACE_MS = 500이랑 closeAfterTurnEnded를 넣음. handleServerMessageupdate?.case === "turnEnded"면 타이머. 500ms 뒤 expectedClose=true 하고 스트림/http1Connection close.

막힘. 타이머 가드가 if (this.expectedClose || this.emittedTerminal) return. turnEnded는 매핑이 done을 push함. live-transport.ts:567 message.type === "done" || "error"emittedTerminal = true. 패치 삽입 위치가 매핑 전이라 타이머는 걸림. 500ms 뒤엔 이미 terminal. 가드가 close를 스킵. 행이 그대로임. senpi #1062를 안 고침. emittedTerminal 스킵은 EOF 핸들러 :1132-1137 논리임. 거기선 두 번째 터미널 금지. 여기선 done 이후에 소켓을 놓는 게 목적. 가드를 expectedClose만 남겨라. emittedTerminal이면 닫아야 함.

더. (1) cancelCursorRun :774-788NGHTTP2_CANCEL + session?.close(). 이 헬퍼는 stream.close()만. 세션 누수 가능. 호출자가 나중에 close() 하면 경합. 세션도 같이 닫거나, cancelCursorRun을 재사용하되 CANCEL RST는 쓰지 말 것. 그레이스풀 close가 목적. (2) 500ms는 senpi 5s exec drain이 아님. trailing usage/checkpoint용. native exec가 turnEnded 이후에도 돌면 잘림. 기본 nativeLocalExec off라 지금은 작음. (3) 클라이언트 툴 경로는 turnEnded가 안 와서 타이머가 안 걸림. 그 부분은 맞음. 테스트로 mcpArgs suspend가 close 타이머를 안 거는 걸 잠가라. (4) 베이스가 #2320. dev에 단독 머지 금지. 하이진이 테스트 없다고 이미 실패. emittedTerminal 가드가 노옵인 걸 재현하는 테스트가 그 실패의 실체임.

types.ts/config.ts 안 만짐. 스플릿 안 씹힘. 리베이스하지 말고 닫으라는 케이스 아님. #2320 머지 전에 이 PR만 올리지 말 것. #2307 clean Connect EOF settle이랑 합치지 말 것. T24(EOF without turnEnded fail)를 여기 넣지 말 것. 헤드 주석이 합성 done을 유지하라고 함. #2318이랑 파일 안 겹침. #2188 L1–L9 사이드카 + routed vision(#2306) 이미 dev. x_search 넣지 말 것. Grok OAuth Chat 기본(#2255)이랑 다른 레인임. 프리뷰 배포 아님. 카탈로그는 Ox Alpha x-preview-f-free + deepseek-v4-flash-vision-exp. v2.29.0 태그됨. v2.30.0-preview.20260821 있음. 비전공자 유지. 의도는 행 수정인데 타이머가 노옵 + 드래프트 + 테스트 없음이라 61.

해결방안: 드래프트 유지. closeAfterTurnEnded에서 emittedTerminal 스킵 삭제. expectedClose만 가드. done push 이후에도 스트림을 닫아라. session도 정리. 클라이언트 툴 suspend(:203-206)는 타이머 없이 유지. 회귀: (1) turnEndeddone 이미 나간 뒤 500ms 안에 stream.close 호출 (2) 클라이언트 툴 경로 타이머 없음 (3) EOF 합성 done 경로 유지. 테스트 파일 추가 전 하이진 통과 안 됨. #2320 머지 후 dev에 리베이스하거나 스택 유지. 라벨 건드리지 말 것. 스플릿이 live-transport.ts를 옮기면 리베이스하지 말고 닫고 다시 짜라. 지금은 그 정도 아님.

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

…not stall the turn

T03 (senpi #1062): after the server sends turnEnded, the application turn
is complete. A server that keeps the HTTP/2 stream open past this point
cannot hold the turn hostage until a 300s bridge idle timeout. Close our
side after a 500ms grace so trailing frames (late usage, checkpoint) still
land before we release the socket.

The grace timer only checks expectedClose (client-tool suspend cancel);
emittedTerminal is deliberately not checked because finalizeTurnEvents sets
it synchronously during turnEnded mapping, before the timer fires.

The client-tool suspend path (live-transport.ts:203-206) intentionally ends
without waiting for turnEnded and is not affected by this change.
@lidge-jun
lidge-jun force-pushed the codex/senpi-cursor-t02 branch from 2828efc to 90dd314 Compare August 22, 2026 01:46
@lidge-jun
lidge-jun marked this pull request as ready for review August 22, 2026 01:48
@lidge-jun
lidge-jun merged commit fd06058 into dev Aug 22, 2026
23 of 25 checks passed

@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: 90dd314798

ℹ️ 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".

this.http1Connection.close();
} else {
try {
this.stream?.close();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Settle the turn instead of only closing the Bun stream

Although the previous emittedTerminal guard is now removed, a held-open HTTP/2 response can still hang under Bun: in the available Bun runtime, ClientHttp2Stream.close() marks the stream closed but does not emit the end or error event that open() relies on to invoke its settler, so run() remains blocked after this timer fires. Explicitly settle after queued frame work drains, or force destroy() and cover this with an H2 fixture that writes turnEnded without calling stream.end().

AGENTS.md reference: src/AGENTS.md:L7-L9

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working intake: hygiene-blocked Deterministic PR hygiene checks failed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant