You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
codex-proxy: a failed codex run is reported as a successful 'stop' — close handler ignores exit code and signal in both modes #676
bin/codex-proxy.mjs never checks whether the spawned codex exec child actually succeeded. Its child.on('close', (code, signal) => ...) handler (bin/codex-proxy.mjs:401) ignores both code and signal, does not track stderr (no stderrTail — the stderr handler at :395 only logs), and unconditionally reports success: non-stream mode returns HTTP 200 with finish_reason: "stop", and stream mode emits a finish_reason: "stop" chunk + data: [DONE].
So a run that fails — auth expired, model rejected, CLI crashed — is indistinguishable from a successful empty (or partial) reply for the identical request.
This is the codex-proxy counterpart of the two defects already fixed in the sibling bin/muse-proxy.mjs: #670 (streaming failure invisible to SSE clients) and #672 (failure masked whenever any content exists). The muse-proxy fix judged failure on close and framed stream errors as SSE; codex-proxy has no failure detection at all (noted as "a different gap, not filed" in the #670 run's backlog entry).
stream: content chunk partial answer, then a finish_reason: "stop" chunk + [DONE]
Stub success case (agent_message + turn.completed, exit 0) correctly returns 200 with the full text in both modes.
Dedup: searches for "codex-proxy" across open/closed issues return only #434/#435/#437/#480/#483/#496/#661/#670 — none covers exit-code/failure detection. Cross-repo: bin/codex-proxy.mjs exists only in this repo (no copy in developing-today/opencode, developing-today/openchamber, dezren39/nix).
Why it matters
OpenCode consumes this bridge in streaming mode as a provider. A failed Codex run presented as a clean stop with empty or truncated text is worse than an error: downstream code and users treat it as the complete answer, and an auth expiry presents as "the model said nothing" instead of a diagnosable failure.
Acceptance criteria
A child that exits non-zero (or is killed by a signal) is reported as a failure in both modes, regardless of how much content was already produced: non-stream returns HTTP 500 with an error.message naming the failure (last stderr line when present, otherwise a fallback naming the signal or exit code — never code null/code -2 style artifacts); stream mode emits a data:-framed error event followed by data: [DONE] and no chunk claims finish_reason: "stop".
Successful runs (exit 0) are unchanged in both modes.
A regression test with a stub codex (fail-with-no-output and fail-after-partial-output cases, both modes) passes; the existing proxy test suites still pass.
Activity
fix(codex-proxy): report failed codex runs as errors, not successful …