Skip to content

codex-proxy: a failed codex run is reported as a successful 'stop' — close handler ignores exit code and signal in both modes #676

Closed

Description

@dezren39

Finding

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).

Evidence (reproduced on main d8bfe7c)

Ran bin/codex-proxy.mjs with a stub codex at $HOME/.local/bin/codex (the proxy's own binary resolution) that reads the prompt from stdin:

  • Stub prints stub codex: auth expired to stderr and exits 3, no stdout:
    • non-stream: HTTP 200, content: "", finish_reason: "stop", completion_tokens: 0
    • stream: HTTP 200, one chunk with finish_reason: "stop", then data: [DONE] — no error anywhere
  • Stub emits one item.completed agent_message (partial answer), prints stub codex: run failed midway to stderr, exits 3:
    • non-stream: HTTP 200, content: "partial answer", finish_reason: "stop"
    • 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions