Skip to content

fix(cursor): retry a discovery stream that ends before HTTP/2 headers - #3102

Merged
lidge-jun merged 1 commit into
devfrom
codex/3051-cursor-preheader-eof
Sep 1, 2026
Merged

fix(cursor): retry a discovery stream that ends before HTTP/2 headers#3102
lidge-jun merged 1 commit into
devfrom
codex/3051-cursor-preheader-eof

Conversation

@lidge-jun

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

Copy link
Copy Markdown
Owner

Summary

Carries #3052 (author @terrytan95) rebased onto current dev. The patch is one production line and needs no changes.

Cursor discovery over HTTP/2 starts with status = 0 and only assigns it on response. When a stream ends before headers arrive — which real Cursor endpoints do intermittently — req.on("end") fell through every status branch to { error: "http", detail: "HTTP unknown" }. The retry set is timeout|transport (src/adapters/cursor/live-models.ts:54), so http was never retried and the catalog recorded a discovery failure for a connection whose very next request succeeds. The HTTP/1.1 path already maps a fetch failure to transport at :194-196; this makes the HTTP/2 path agree.

Closes #3051.

Verification

bun test tests/cursor-hardening.test.ts   -> 42 pass / 0 fail / 89 expect()
bun x tsc --noEmit                        -> exit 0

The test is not a stub: it stands up a real HTTP/2 server, closes the first stream with NGHTTP2_NO_ERROR before responding, and asserts both that a second request happened and that the model list came back. Mutation-checked — deleting the single production line gives 41 pass / 1 fail, exactly retries an HTTP/2 stream that ends before response headers, then restored to 42/0.

The deterministic-auth test directly above it is the control: 401/403 and non-2xx statuses stay non-retryable, so this widens retry only to the pre-header case.

Checklist

  • Focused tests for the changed subsystem pass
  • bun x tsc --noEmit clean
  • Regression test present and mutation-verified
  • No docs-site change needed (internal transport classification)

Triaged in the 2026-08-31 non-priority-70 bug round; supersedes #3052 only by rebase.

Summary by CodeRabbit

  • Bug Fixes
    • Improved handling of interrupted HTTP/2 connections while retrieving available models.
    • These interruptions are now correctly classified as transport errors, enabling the request to retry instead of reporting an misleading HTTP or authentication error.
    • Confirmed successful recovery when a subsequent attempt returns a valid response.

@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner August 31, 2026 18:19
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-31T18:22:06.458240Z 2b11e98 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

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

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

@coderabbitai

coderabbitai Bot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 7376d733-2495-4ac3-b02c-5e6713df702e

📥 Commits

Reviewing files that changed from the base of the PR and between 9d122dd and 2b11e98.

📒 Files selected for processing (2)
  • src/adapters/cursor/live-models.ts
  • tests/cursor-hardening.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 5 remain after this review.


📝 Walkthrough

Walkthrough

The Cursor HTTP/2 adapter now classifies pre-header stream termination as a transport error. The added test confirms that model discovery retries the failed request and returns the model from the next successful attempt.

Changes

Cursor pre-header retry

Layer / File(s) Summary
Classify and retry pre-header stream termination
src/adapters/cursor/live-models.ts, tests/cursor-hardening.test.ts
At line 265, fetchCursorUsableModelsHttp2Once returns a transport error when the stream ends before response headers. The test at lines 427–445 closes the first stream with NGHTTP2_NO_ERROR, verifies a second request, and confirms the successful model result.

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

Merge Risk: ⚪ Minimal · up to 2b11e

The change retries HTTP/2 discovery streams that end before response headers, preventing intermittent catalog failures while leaving authenticated and other HTTP errors unchanged. Focused tests and type checking pass, so no actionable merge-blocking risk remains.

Suggested reviewers: ingwannu, yansigit

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: retrying a Cursor discovery stream that ends before HTTP/2 response headers arrive.
Linked Issues check ✅ Passed The implementation addresses issue #3051 by classifying pre-header HTTP/2 stream termination as a transient transport error. The existing retry path can then retry the request, and the regression test…
Out of Scope Changes check ✅ Passed The changes are limited to the Cursor HTTP/2 discovery adapter and its regression test. Both files directly support the linked issue and PR objective. No unrelated code or behavior changes are present…
Full details: Linked Issues check

Explanation

The implementation addresses issue #3051 by classifying pre-header HTTP/2 stream termination as a transient transport error. The existing retry path can then retry the request, and the regression test verifies that a second request succeeds. The changes preserve the existing handling for completed HTTP responses and other discovery outcomes.

Full details: Out of Scope Changes check

Explanation

The changes are limited to the Cursor HTTP/2 discovery adapter and its regression test. Both files directly support the linked issue and PR objective. No unrelated code or behavior changes are present.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/3051-cursor-preheader-eof

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.

@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 60 / 80

이 PR은 Cursor 라이브 모델 발견이 HTTP/2에서 응답 헤더보다 먼저 스트림이 끝날 때, 실패를 잘못 분류하던 구멍을 고칩니다. 지금 dev HEAD는 9d122ddb1 입니다. 방금 올라간 것은 제공자 마크 유닛을 닫는 문서(#3101)이고, 패키지는 2.39.0 입니다. 런타임 쪽 남은 round-2 큰일은 #3026 포크 롤아웃 복구, #3029 소진된 5시간 풀 선택, #3008 히스토리만 있는 stop abort, #3019 WHAM 401 우회입니다. 이번 PR은 그 목록 밖입니다. 본문도 2026-08-31 non-priority-70 버그 라운드에서 골랐다고 적혀 있습니다. 베이스는 dev, 헤드 브랜치는 codex/3051-cursor-preheader-eof, 라벨은 bug 입니다. 생산 파일 하나와 테스트 파일 하나, 더하기 20줄입니다.

고치는 코드는 생산 한 줄입니다. src/adapters/cursor/live-models.tsfetchCursorUsableModelsHttp2Oncestatus 를 0으로 시작하고, response 이벤트가 와야 :status 를 넣습니다. 헤더 없이 end 가 오면 지금 HEAD는 status !== 200 분기로 떨어져 { error: "http", detail: "HTTP unknown" } 을 냅니다. 바깥 fetchCursorUsableModels 의 재시도 집합은 54줄의 RETRYABLE_DISCOVERY_ERRORS 이고 값은 timeouttransport 뿐입니다. http 는 한 번도 다시 시도하지 않습니다. 카탈로그는 발견 실패를 기록하고 오래된 목록이나 정적 모델로 내려갑니다. 대시보드에는 Discovery failed 가 뜨는데, 같은 Cursor OAuth 계정으로 바로 다음 요청은 성공합니다. #3051 이 그 증상입니다. 이슈에 적힌 로그도 [http]: HTTP unknown 과 바로 다음의 성공(204 모델)이 같이 나옵니다.

HTTP/1.1 길은 이미 다릅니다. fetchCursorUsableModelsHttp1Once 는 fetch 가 던지면 타임아웃이 아닐 때 { error: "transport", detail: "HTTP/1.1 request failed" } 로 닫습니다(194-196줄). HTTP/2만 헤더 전 종료를 http 로 보고 있었습니다. 이번 한 줄은 status === 0 이면 error: "transport", 설명은 HTTP/2 response ended before headers 로 닫아서 두 길이 같게 만듭니다. 401/403은 그대로 auth, 헤더가 온 뒤의 비-200은 그대로 http 라서 재시도 범위가 넓어지지 않습니다. 재시도는 이미 있는 1회뿐입니다. 250-500ms 지터 뒤에 같은 URL로 다시 보내고, 두 번째 기한은 min(timeoutMs, 3000) 입니다. 새 루프나 새 상수를 만들지 않았습니다.

테스트는 tests/cursor-hardening.test.ts 의 Cursor discovery bounded retry 블록에 한 개를 넣습니다. 진짜 HTTP/2 서버를 세우고, 첫 스트림은 NGHTTP2_NO_ERRORrespond() 전에 닫고, 두 번째는 protobuf 200과 gpt-5.5-high 를 줍니다. requests 가 2이고 결과가 성공인지 봅니다. 본문이 말한 대로 생산 한 줄을 지우면 이 테스트만 깨집니다. 바로 아래 테스트는 401/403과 완료된 non-2xx를 재시도하지 않는 대조입니다. 타임아웃 재시도 테스트만 1초 기한을 쓰고, 이번 테스트는 기본 기한입니다. 헤더 전 종료는 바로 end 가 오므로, 타임아웃 테스트처럼 기한을 줄이지 않아도 됩니다.

이 패치는 @terrytan95 의 #3052를 지금 dev 에 다시 올린 것입니다. 커밋은 하나이고 cherry-pick 메모가 있습니다. 생산 줄은 #3052 때와 같습니다. types.ts/config.ts 분할과는 무관합니다. Cursor 런타임 열차(#2865 엔벨로프, #2876 call-id, #2900/#2903/#2910/#2936/#2940 툴결과/체크포인트, #3028 클라이언트 툴 이름)와도 파일이 겹치지 않습니다. 발견 전용 cursorH2Pool 만 씁니다. Run 경로의 긴 스트림은 이 풀을 쓰지 않습니다. 점수는 60입니다. 라이브 Cursor 카탈로그가 간헐적으로 Discovery failed 로 떨어지는 실사용 버그입니다. 고치는 면적은 한 줄이고 테스트가 진짜 HTTP/2로 구멍을 잠급니다. 다만 지금 dev 의 70대 남은 일은 위 네 개라서, 이 PR을 그 열차와 같은 급으로 두지는 않습니다.

라인 265 - status === 0transport 로 닫는 한 줄은 맞습니다. 지금 HEAD의 status !== 200status || "unknown" 으로 HTTP unknown 을 만들던 길을 먼저 끊습니다.
라인 238 / 243 - status 는 0으로 시작하고, response 에서 Number(headers[":status"] ?? 0) 을 넣습니다. response 는 왔는데 :status 가 비면 숫자 0이 되어 이번 분기에서 재시도됩니다. 기형 헤더를 전송 실패로 한 번 더 시도하는 셈입니다. 막지는 않습니다.
라인 251-261 / 265 - data 가 헤더보다 먼저 오고 이어서 end 가 오면, 청크가 있어도 status === 0 이라 transport 로 재시도합니다. HTTP/2에서는 거의 없는 위반이고, 재시도가 더 안전한 쪽입니다.
라인 54 / 70 - RETRYABLE_DISCOVERY_ERRORS 는 그대로 timeout|transport 입니다. 새 재시도 정책을 만들지 않고, 분류만 고쳐서 기존 1회 재시도가 살아납니다.
라인 427-445 / tests/cursor-hardening.test.ts - 이슈 #3051 에 적힌 NGHTTP2_NO_ERROR 픽스처를 그대로 옮겼습니다. 첫 시도의 error: "transport" 문자열은 잠그지 않습니다. 바깥 함수가 첫 실패를 삼키므로 최종 성공만 보는 것이 맞습니다. 생산 줄을 지우면 이 테스트만 깨진다는 주장이 그 빈칸을 메웁니다.
경로 src/adapters/cursor/h2-pool.ts cursorH2Pool - 스트림만 닫히면 세션은 그대로 재사용됩니다. GOAWAY 가 와야 drain 합니다. 재시도가 같은 풀 세션을 다시 쓰는 구조입니다. 테스트의 두 번째 요청이 성공하므로 이 픽스처에서는 세션이 살아 있습니다. 서버가 세션을 빼는 중이면 두 번째도 실패할 수 있고, 그때는 기존 1회 한도 그대로 카탈로그 실패가 남습니다.
경로 fetchCursorUsableModelsHttp1Once - fetch 는 헤더가 와야 Response 를 줍니다. 헤더 전 종료는 네트워크 예외로 transport 가 됩니다. 이번 PR 범위 밖이고, 1.1 핀 사용자 보고도 #3051 에 없습니다.
경로 #3052 - 원본 PR이 아직 OPEN입니다. 이번 것은 rebase-only 대체입니다. 머지 뒤에 원본을 열어 두면 열린 PR 수가 부풉니다.

메인테이너의 판단이 필요한 지점

너의 추천
CI가 초록이면 #3102를 dev 에 squash merge 하세요. 생산 한 줄 + 이슈를 그대로 옮긴 HTTP/2 픽스처라서 범위가 깨끗하고 #3051을 닫습니다. types/config 분할과 무관하니 close-don't-rebase 대상이 아닙니다. 머지 직후 #3052에 Landed via #3102 at <commit> 를 달고 landed-via-maintainer 로 닫으세요. #3051이 Closes로 안 닫히면 같은 방식으로 닫으면 됩니다. 라벨은 바꾸지 않습니다.

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

@lidge-jun

Copy link
Copy Markdown
Owner Author

Administrator bypass record\n\nOwner authorization was explicit in Codex session 01a05a34-1e3a-73f2-8607-15e517cbec11 on 2026-09-01 KST. Exact head: 2b11e98. Current exact-head check rollup: 0 failing, 0 pending. Independent A-gate review found no unresolved code blocker. Using the maintain/admin pull_request bypass documented in MAINTAINERS.md because the author cannot approve their own PR. This bypass does not waive CI or security review.

@lidge-jun
lidge-jun merged commit a3656a9 into dev Sep 1, 2026
27 checks passed
@lidge-jun
lidge-jun deleted the codex/3051-cursor-preheader-eof branch September 1, 2026 00:08
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.

2 participants