Skip to content

fix(test): raise the native passthrough reset from a requested pull - #5128

Merged
lidge-jun merged 1 commit into
devfrom
codex/fix-5073-reset-fixture-consumer
Sep 19, 2026
Merged

lidge-jun merged 1 commit into
devfrom
codex/fix-5073-reset-fixture-consumer

Conversation

@lidge-jun

@lidge-jun lidge-jun commented Sep 19, 2026

Copy link
Copy Markdown
Owner

Summary

  • tests/server/server-auth.test.ts fails intermittently on the line where the fixture deliberately resets its own upstream stream, and it fails as an unhandled error rather than an assertion. It has fired on four unrelated heads — fix(responses): gate post-header reset recovery on SSE protocol state #4989, fix(codex): let a request-owned main bearer take part in pool ordering #5024, dev at ecd3adae75, and contributor PR fix(codex): keep routed rows from inheriting experimental context #5085 whose change was thirteen lines in an unrelated subsystem.
  • The fixture leaked its ReadableStreamDefaultController out of start() and called controller.error() from the test body. The stream lives on the synthetic Bun.serve upstream, one real HTTP hop away from the proxy, so the only thing that can ever observe that Error object is Bun's own response-body sink — the object cannot cross the socket, which is why the report carries the stack of line 4264 while the proxy still delivers its synthetic failed terminal. Erroring a leaked controller happens at a moment of the test's choosing, which is not necessarily a moment when anything is reading: between the sink's reads there is no pending read request to reject, and the rejection's only subscriber is whatever the runtime attaches next. On a busy runner the unhandled-error report wins that race. A downstream read on the proxy's response does not prove the upstream sink currently has a handled read outstanding.
  • The reset is now raised from inside pull() on a stream constructed with highWaterMark: 0. The load-bearing property is that the pull algorithm's promise is always observed: CallPullIfNeeded attaches its own rejection handler and routes the failure into ReadableStreamDefaultControllerError, so a throw from pull() cannot be an orphaned rejection whatever the consumer is doing. highWaterMark: 0 then keeps the reset in a faithful place — the queue is never stocked ahead of demand, so pull() runs if and only if a read request is outstanding, and the stream is never errored before anything has attached to it, which is the state where the error has nowhere to go. The opening chunk is unaffected, because enqueue() ignores the high-water mark.
  • This is the pattern the rest of the suite already uses for a mid-flight upstream failure — tests/responses/sse-failed-tail.test.ts, tests/server/stream-aborted-marker.test.ts, tests/responses/sse-client-frame-bounds.test.ts and others all error from a pull() the consumer requested. The highWaterMark: 0 part is the addition: streamThatFailsMidStream() in stream-aborted-marker.test.ts enqueues on the first pull and errors on the second, and with the default high-water mark that second pull is reached by desired size alone rather than by a waiting consumer.
  • No assertion was relaxed, no budget widened, no retry added and nothing swallowed. What the code under test sees is unchanged: one SSE chunk, then a mid-stream body error. The test still asserts 502, closeReason: "terminal", terminalStatus: "failed", streamAborted: true, one pool failure and a single dispatch.
  • The shared fixture moved to tests/helpers/deferred-reset-sse-upstream.ts because tests/server/server-auth.test.ts sits exactly at its file-size-baseline.json cap of 4589 lines, so the explanation could not be written inline. The test file is now 4581 lines, which the ratchet reports as SHRANK and does not treat as an offender, so no baseline edit is needed. Helpers are support-only and need no test-layout entries.

What this guarantees, and what it only makes less likely

It guarantees that the fixture's reset is never raised anywhere the stream machinery does not already observe it, and that the stream is never errored before a consumer has attached and asked for a chunk. That removes the window the reported failure needs.

It does not prove Bun has no other unhandled path in its server-side sink; that code is not in this repository. It is also not a claim that a read request is still pending at the instant of the throw — a consumer that cancels in between removes its own request — only that the rejection is consumed either way. The argument that such a window exists at all is the observed intermittency itself: if erroring a response-body stream were reported unconditionally, this test would have failed every run rather than four times.

I also looked for the opposite finding, that the proxy's own reset path leaves an unhandled rejection, and did not find one. The production path attaches its consumers explicitly: both tee branches install void reader.closed.catch(...) in src/server/inspection-tee.ts, the inspection consumer is started before the response is returned and reads continuously, client delivery catches body reads and emits a failed SSE tail in src/server/relay.ts, and a mid-stream failure is explicitly non-replayable there. src/lib/abort.ts already documents and guards the analogous Bun fetch-body attachment gap on the client side.

Verification

  • No local suite was run. This repository verifies on hosted CI, and a past local run destroyed real user state under ~/.opencodex; the change was reviewed by static reasoning against the stream specification and against the existing fixture patterns in this suite instead.
  • Static walk of the algorithms: during start() the stream is not yet started, so the opening enqueue cannot pull. When start() settles there is no reader and desired size is -1. The sink's first read is served from the queue and CallPullIfNeeded still declines, because no request is pending and desired size is 0. The second read registers its request and pull() runs. The only behavioural difference from the old fixture is when the stream errors relative to the sink's demand.
  • Repository gates, checked by reading the scripts rather than running them: the layout guards inspect only *.test.ts and exclude tests/helpers/; the new 63-line helper is NEW_OK for the ratchet and the test file SHRANK; structure:check enumerates src/ ownership and this adds no source area; nothing in the helper matches a privacy:scan pattern.
  • Hosted CI on the exact head is the evidence for this PR; test 1/4..4/4 and macos 1/2..2/2 are the checks that carry the file.
  • Proving a flake fixed is hard by construction. A green run here is consistent with the fix and is not by itself proof of it, since the failure needed a loaded runner to appear at all.

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.

Closes #5073

Summary by CodeRabbit

  • Tests
    • Improved coverage for event-stream connections that reset during an active response.
    • Verified that native passthrough failures continue to return a 502 response and generate the expected logging.
    • Confirmed connection pool handling remains correct after upstream resets.
    • Added more reliable scenarios for deferred connection resets, including reset timing and response behavior.

@lidge-jun
lidge-jun requested a review from Ingwannu as a code owner September 19, 2026 08:05
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: lidge-jun/opencodex/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Advanced

Run ID: ab0e7b97-296e-4b43-b128-47f1bba65e92

📥 Commits

Reviewing files that changed from the base of the PR and between 276cee3 and c2b3323.

📒 Files selected for processing (1)
  • tests/helpers/deferred-reset-sse-upstream.ts

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


📝 Walkthrough

Walkthrough

The changes add a reusable deferred SSE reset helper. The native passthrough reset test uses the helper instead of directly erroring a stream controller.

Changes

SSE reset fixture

Layer / File(s) Summary
Deferred reset helper
tests/helpers/deferred-reset-sse-upstream.ts
Defines DEFERRED_RESET_MESSAGE and DeferredResetSseUpstream. The helper creates a zero-high-water-mark ReadableStream, enqueues SSE chunks, and throws the reset message from pull() after reset() resolves the reset signal.
Server auth test integration
tests/server/server-auth.test.ts, tests/helpers/deferred-reset-sse-upstream.ts
The native passthrough reset test imports the helper, returns upstream.response(), and calls upstream.reset() to trigger the deferred reset.

Priority: ⬇️ Low

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

Change: Other · Severity of issue fixed: Low

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR meets the coding requirements in [#5073]. tests/helpers/deferred-reset-sse-upstream.ts:45-63 creates the SSE body, uses highWaterMark: 0, waits for reset() in pull(), and throws the fix…
Out of Scope Changes check ✅ Passed The changes stay within [#5073]. The new helper in tests/helpers/deferred-reset-sse-upstream.ts implements the required reset fixture and documents the unhandled-error failure mode. The import and f…
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 1 functions across 2 files.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main test-fixture change: raising the native passthrough reset from a requested stream pull. It is concise and related to the pull request objectives.
✨ Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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 bug Something isn't working label Sep 19, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Deterministic PR hygiene checks passed.

…5073)

The reset fixture in tests/server/server-auth.test.ts leaked its stream
controller out of start() and errored it from the test body. Whether that
rejection had a consumer depended on where Bun's server-side response sink
happened to be: between its reads there is no pending read request to reject,
so on a loaded runner the fixture's own error escaped as an unhandled error
and failed the whole file. It fired on four unrelated heads (#4989, #5024,
dev at ecd3ada, and #5085).

Raise it from inside pull() on a stream whose high-water mark is zero
instead. shouldCallPull is then true only while a read request is
outstanding, so pull() runs if and only if a consumer is waiting for the
next chunk, and throwing there rejects that read request. The reset now has
a consumer no matter when the test calls it. What the code under test sees
is unchanged: one SSE chunk, then a mid-stream body error.

Closes #5073
@lidge-jun
lidge-jun force-pushed the codex/fix-5073-reset-fixture-consumer branch from 276cee3 to c2b3323 Compare September 19, 2026 08:15
@lidge-jun

Copy link
Copy Markdown
Owner Author

리뷰 · 우선순위 78 / 80

이 PR은 server-auth.test.ts 안의 “네이티브 패스스루 업스트림이 중간에 끊기면 502·풀 페널티가 남는지” 검사 픽스처가 가끔 테스트 본문에서 터지던 플레를 고칩니다. 예전에는 start()에서 ReadableStreamDefaultController를 밖으로 빼 두고, 테스트가 controller.error()를 직접 불렀습니다. 그런데 Bun 업스트림 싱크에 대기 중인 read가 없을 때 그 에러는 스트림이 삼키지 못하고 언핸들드 리젝션으로 남을 수 있습니다. 그래서 #4989, #5024, dev의 한 커밋, 관련 없는 #5085까지 네 번이나 이 파일 전체가 깨졌고, 이슈 #5073을 닫습니다.

고침은 컨트롤러를 밖으로 빼지 않는 것입니다. 새 헬퍼 tests/helpers/deferred-reset-sse-upstream.ts는 청크를 start()에서만 enqueue하고, reset()은 공용 Promise.withResolvers를 resolve합니다. 실제 throwpull() 안에서 일어납니다. 스트림 스펙상 pull()의 프로미스는 항상 관찰되므로 orphan rejection이 될 수 없고, highWaterMark: 0이라 pull은 “누가 다음 청크를 요청했을 때”만 돕니다. 첫 SSE 청크는 큐에서 그대로 나가고, 그다음 바디 에러가 옵니다. 어서션(502, terminal failed, streamAborted, 풀 실패 1회, 디스패치 1회)은 그대로입니다. server-auth.test.ts가 file-size-baseline 상한(4589줄)에 딱 붙어 있어서 설명을 인라인으로 못 넣고 헬퍼로 뺀 것도 맞습니다. 테스트 파일은 줄 수가 줄어 SHRANK이고, 헬퍼는 layout 가드 밖이라 baseline 수정이 필요 없습니다. types.ts/config.ts 분리 캠페인과는 무관한 테스트 전용 변경입니다.

라인 - 실질적인 문제 없음. 헬퍼·호출부 모두 기존 pull() 중도 실패 픽스처 패턴(stream-aborted-marker, sse-failed-tail 등)과 같고, highWaterMark: 0만 이번 플레 창을 막는 추가입니다. DEFERRED_RESET_MESSAGE로 메시지를 고정한 것도 어서션에 쓰기 좋습니다.

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

  • 호스티드 CI(특히 test 1/4..4/4, macos 1/2..2/2)가 이 head에서 초록인지. 플레 수정은 초록 한 번이 “고침 증명”은 아니지만, 회귀 없음의 증거로는 충분합니다.
  • 같은 패턴의 다른 leaked-controller 픽스처가 스위트에 더 남아 있는지는 이번 범위 밖입니다. 필요하면 후속 이슈로 훑어도 됩니다.

너의 추천
호스티드 CI가 초록이면 머지하세요. Closes #5073이 본문에 있으니 머지 후 이슈도 함께 닫힙니다. 로컬 스위트는 돌리지 마세요(홈 파괴 이력이 있음).

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

@lidge-jun

Copy link
Copy Markdown
Owner Author

추가 리뷰 · 우선순위 78 / 80

이 PR이 하는 일은 이전과 같습니다. server-auth.test.ts의 네이티브 패스스루 검사가, 업스트림이 중간에 끊기면 502와 풀 페널티를 남기는지 봅니다. 예전 픽스처는 스트림 컨트롤러를 밖으로 빼서 controller.error()를 직접 불렀고, 읽는 쪽이 없을 때 그 에러가 테스트 전체를 죽이는 플레가 났습니다. 지금은 헬퍼 tests/helpers/deferred-reset-sse-upstream.tspull() 안에서 던집니다.

이번 헤드는 276cee3c2b3323으로 다시 쓴 것입니다. tests/server/server-auth.test.ts는 두 커밋의 내용이 같습니다. 바뀐 것은 헬퍼 맨 위 주석뿐입니다. 예전 주석은 reset()을 언제 불러도 그 순간 기다리는 읽기가 있어서, 에러가 그 리더에게 간다고 적었습니다. 싱크가 붙기 전에도, 읽기와 읽기 사이에도 그렇다고 했습니다. 그 말은 셉니다. pull()이 시작한 뒤 소비자가 취소하면 그 읽기 요청은 사라집니다. 새 주석은 둘로 나눕니다. pull()이 던진 값은 스트림이 항상 받아서, 아무도 안 듣는 리젝션으로 남지 않습니다. highWaterMark: 0은 그 던지기가 다음 청크를 요청한 뒤에만 일어나게 합니다. 테스트가 기대하는 것(502, terminal failed, streamAborted, 풀 실패 1회, 디스패치 1회)은 그대로입니다.

이 헤드의 호스티드 CI도 끝났습니다. test 1/4부터 4/4, macos 1/22/2, 묶음 ci가 통과했습니다. 초록 한 번이 플레가 사라졌다는 증명은 아닙니다. 이 파일에서 회귀가 없었다는 증거로는 됩니다.

라인 - 동작 버그 없음. 주석만 고친 푸시이고, 고친 방향이 맞습니다. 없는 보장을 지우고, 스트림이 실제로 하는 일만 남겼습니다.

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

  • 머지는 REVIEW_REQUIRED 때문에 막혀 있습니다. 필수 체크가 실패해서 막힌 것이 아닙니다.
  • 목록에 enforce-targetlabel이 취소로 남아 있습니다. 포스 푸시 중에 이전 잡이 끊긴 것이고, 베이스는 이미 dev입니다.

너의 추천
머지하세요. 본문에 Closes #5073이 있으니 이슈도 같이 닫힙니다. 로컬 스위트는 돌리지 마세요. 홈 디렉터리를 망가뜨린 적이 있습니다.

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

@lidge-jun
lidge-jun merged commit f39ba5a into dev Sep 19, 2026
29 of 34 checks passed
@lidge-jun
lidge-jun deleted the codex/fix-5073-reset-fixture-consumer branch September 19, 2026 10:10
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