Repository navigation
Windows segfault (Bun #32111): passthrough SSE relay crashes the proxy on client abort #31
Description
Summary
On Windows x64, the proxy process segfaults (hard crash, takes down the whole server + every in-flight turn) during openai-responses passthrough streaming. It is intermittent — observed anywhere from ~7s to ~65s after ocx start, always during/after a streamed /v1/responses call where Codex disconnects or aborts mid-stream.
The crash itself is a Bun runtime bug (oven-sh/bun#32111), but opencodex's passthrough relay is a perfect trigger for it, and the openai passthrough path is the most common one (ChatGPT login). Filing here so it's tracked + so we can sidestep it in opencodex independently of Bun.
Environment
- opencodex
2.1.8 - Bun
1.2.23Windows x64 — also reproduces through Bun1.3.14(latest) per #32111; the official fix (#32120) is not released yet. - Provider:
openai-responses,authMode: forward(ChatGPT login) → hits the passthrough branch.
Crash
panic(main thread): Segmentation fault at address 0xFFFFFFFFFFFFFFFF
oh no: Bun has crashed. This indicates a bug in Bun, not your code.
https://bun.report/1.2.23/ea1cf13671IshkUuk6m3Dg28xX00l2hB___A2DD
Decoded stack (identical across runs):
memcpy.asm:201 memcpy
std/array_list.zig:315 appendSliceAssumeCapacity
std/array_list.zig:304 appendSlice
src/collections/baby_list.zig:391 write
src/bun.js/webcore/streams.zig:1028 write ← ReadableStream internal write
src/bun.js/webcore/Sink.zig:397 write ← JS controller.enqueue() → native
src/bun.js/jsc/host_fn.zig:19 function ← JS callback into native
i.e. JS calls controller.enqueue(chunk) → Sink.write → memcpy into a freed buffer.
Root cause
The passthrough path wraps the upstream fetch body in a JS ReadableStream whose async pull() awaits then enqueues — in relaySseWithHeartbeat (src/server.ts, the SSE branch) and relayWithAbort (non-SSE):
// relaySseWithHeartbeat, called from the passthrough branch (src/server.ts ~319-326)
async pull(controller) {
const { done, value } = await reader.read(); // ← yields the event loop
inspectChunk(value);
controller.enqueue(value); // ← crashes: sink already torn down
}This is exactly the pattern that triggers oven-sh/bun#32111: when the client aborts during the await reader.read(), Bun's onAbort tears down the response stream's sink; the resuming controller.enqueue() then dereferences freed memory → segfault. The code is spec-valid; a conformant runtime must not crash on it. But it's unfixed in every released Bun through 1.3.x on Windows, so opencodex needs to sidestep it.
#32111's ablation is decisive about the trigger:
pull shape |
Crash? |
|---|---|
async pull with await |
✅ crashes |
sync pull (no await) |
❌ no crash |
Note: naively moving the await into a pump spawned by a sync pull does not help — the await→enqueue race is still present (verified locally; crash signature unchanged).
Minimal repro (crashes Bun on its own, from #32111):
Bun.serve({ port: 8090, fetch() {
return new Response(new ReadableStream({
async pull(c) { await Bun.sleep(0); c.enqueue(new Uint8Array(8)); c.close(); },
}));
}});
// + clients that abort mid-streamImpact
- Affects the default/most common path: any
gpt-*model over ChatGPT login routes throughopenai-responsespassthrough →relaySseWithHeartbeat. - The crash kills the entire proxy process (not just the one request), so every in-flight turn dies and
stream disconnected before completionsurfaces in Codex. - Race-condition-timing, so it's flaky — some users hit it constantly, others rarely, which makes it look environment-specific.
Suggested directions (not prescriptive — your call)
These all preserve the passthrough behavior; the question is how to avoid the JS Sink.write path on the client-facing body.
tee()+ native relay (my current favorite). Tee the upstream body; return branch[0]directly as theResponsebody so Bun relays it natively (never enters the JSSink.writepath); consume branch[1]in the background, read-only, purely forinspectChunk/onTerminalquota tracking. Preserves streaming + terminal-outcome recording; drops the 15s heartbeat on passthrough (upstream SSE already cadences).⚠️ Needs validation on Windows thattee()'s internal pull doesn't itself trip #32111.- Wait for oven-sh/bun#32120 to ship and bump the
engines.bunfloor once it does. Lowest-risk, but blocks Windows users until then. - Gate it. When terminal-outcome recording isn't active, return the body directly (native, crash-free); keep the wrapping path only when
terminalBodyWillRecordis true. Narrows but doesn't eliminate the window.
Relates to the devlog/110_codex-stream-stability work (RC2/RC3 hardening) — the abort/heartbeat guarantees introduced there are exactly what put us on the crashing path, so any fix here should preserve their intent.
Workaround (for affected users now)
For an openai-only setup, returning the upstream body directly in the passthrough branch avoids the crash (native relay), at the cost of the heartbeat + inline terminal recording on that path. Happy to open a PR for option 1 if useful — wanted to flag the root cause first since the upstream code here has evolved past the published 2.1.8 tarball.
Activity
fix: graceful shutdown drain + passthrough tee relay (Bun#32111)
lidge-jun commentedon Jun 25, 2026
Implemented in
1fb4fdbondev— option 1 (tee + native relay):body.tee(): branch[0] goes directly to theResponsebody (Bun native relay, never enters JSSink.writepath); branch[1] is consumed in a backgroundconsumeForInspection()pump for terminal-outcome/quota tracking onlyasync pull → controller.enqueuepattern that triggers Bun#32111 is completely bypassed on the passthrough pathtrackStreamLifetime()ensures the tee'd native body is tracked for graceful shutdown drain (Define a graceful drain policy for in-flight turns during shutdown #30)Full suite: 360/360 pass, tsc clean. Once Bun#32120 ships and we bump the
engines.bunfloor, the tee can be simplified back to a direct relay if desired.feat(devin): fix the request encoding that made every chat turn fail
feat(devin): Cognition cloud provider, carried from #4078 and hardened (
fix: graceful shutdown drain + passthrough tee relay (Bun#32111)
feat(devin): Cognition cloud provider, carried from lidge-jun#4078 an…
fix(devin): resolve models through catalog family metadata (carry #6091…