Skip to content

agy-proxy: failed agy run reported as successful 'stop' — stream handler never checks the exit code, non-stream masks failure when partial output exists #678

Closed

Description

@dezren39

Finding

bin/agy-proxy.mjs reports a failed agy run as a successful completion in most shapes:

  • Stream mode — the child.on('close', code => ...) handler (bin/agy-proxy.mjs:467) checks code only for the resume-retry case (code !== 0 && !hasOutput && allowRetry && targetConvId, :471). Every other non-zero exit falls through to the unconditional success ending at :481-510: a chunk with finish_reason: 'stop' plus data: [DONE]. The stderrBuffer collected at :463-465 is never read anywhere in the stream path, so the failure cause is discarded entirely.
  • Non-stream mode — the failure branch is gated on empty output (if (code !== 0 && !responseText), :553), so a run that fails after producing a partial response returns HTTP 200 with finish_reason: 'stop' and the truncated text. (This is the sighting noted for separate judgement in muse-proxy: a run that fails after producing partial output is reported as a successful 'stop' — failure is masked whenever any content exists #672; the stream half above was not covered there or anywhere else.)

This is the agy-proxy counterpart of the defects already fixed in the sibling bin/muse-proxy.mjs (#670, #672, #674) and bin/codex-proxy.mjs (#676): a failed run presented as a clean stop.

Evidence (reproduced on main 4b85ef69)

Ran bin/agy-proxy.mjs with a stub agy at $HOME/.local/bin/agy (the proxy's own binary resolution) whose behaviour keys on the requested --output-format:

  • Stub prints stub agy: auth expired to stderr and exits 3, no stdout:
    • stream: HTTP 200, one chunk with finish_reason: "stop", then data: [DONE] — no error anywhere
    • non-stream: HTTP 502 {"error":"agy process exited with non-zero code","code":3,"stderr":"stub agy: auth expired"} (correct — the control case)
  • Stub emits one step_update text delta (partial answer) / {"response":"partial answer"}, prints stub agy: run failed midway to stderr, exits 3:
    • stream: HTTP 200, content chunk partial answer, then a finish_reason: "stop" chunk + [DONE]
    • non-stream: HTTP 200, content: "partial answer", finish_reason: "stop"
  • Stub success case (delta + result event / {"response":"full answer"}, exit 0) correctly returns 200 with the full text and stop in both modes.

Dedup: searches for "agy-proxy" / "agy" across open and closed issues return only #434/#435/#437/#483/#661/#672 — #672 notes the non-stream gate at :553 as "judge separately" and explicitly excludes it from its acceptance criteria; none covers the stream handler or failure detection in this proxy. Cross-repo: bin/agy-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 agy run (expired auth, CLI crash, model rejection) 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. The two modes also disagree with each other — and with the proxy's own correct empty-output non-stream case (502 with stderr).

Acceptance criteria

  • A child that exits non-zero (or is killed by a signal — code is null then) is reported as a failure in both modes, regardless of how much content was already produced: non-stream returns an error status (the existing 502 shape, with stderr, is fine) instead of 200/stop; stream mode emits a data:-framed error event naming the failure (last stderr line when present) followed by data: [DONE], and no chunk claims finish_reason: "stop".
  • The resume-retry behaviour is preserved: a failed resume (code !== 0, no output, a conversation was being resumed) still retries once as a fresh conversation before any failure is reported.
  • Successful runs (exit 0) are unchanged in both modes.
  • A regression test with a stub agy (fail-with-no-output and fail-after-partial-output cases, both modes — the bin/codex-proxy.test.mjs / bin/muse-proxy.test.mjs stub pattern; there is currently no bin/agy-proxy.test.mjs) passes; the existing proxy test suites still pass.

Activity

  1. added a commit that references this issue on Oct 23, 2026
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