fix(cli): clear recovered provider errors - #123
Conversation
|
Reviewed and verified — this is the right fix at the right layer, and it's the first of the eight that I'd have written the same way. Merging. Three things worth adding to the record, one of which means the production symptom will not go away when this lands. Root cause confirmed, and the codebase already contains the correct shape
// 507 — proactive: usage came back over the limit
if (!ctx.assistantMessage.summary && isOverflow({ ... })) {
ctx.needsCompaction = true // silent
}
// 641 — reactive: the provider already rejected the request (the 413)
ctx.needsCompaction = true
yield* events.publish(Session.Event.Error, { sessionID: ctx.sessionID, error }) // broadcasts
returnSame recovery, same next step, but one path announces a failure on the error channel and the other doesn't. The CLI then treats every I considered deleting the Verification
The part that isn't finished: cloud still fails the runThis fixes the exit code. The exit code is not what cloud reads. # app.py:2179
if isinstance(event_data, dict) and _event_carries_error(event_data):
seen_error = json.dumps(_extract_event_error(event_data)) # latched, never cleared
...
# app.py:2227
if rc == 0 and not seen_error:
Worth noting the shape sitting immediately above # Finish reason of the most recent LLM call; a truncated call exits rc=0
# with no error event. Overwritten, not accumulated: a run that truncates
# early but finishes cleanly is a success.
last_stop_reason: str | None = NoneThat's exactly the reasoning this PR applies to So the sequencing is: this PR is the precondition, not the fix. Before it, cloud couldn't simply trust I'd want that cloud change reasoned through rather than done reflexively — the question is whether any genuinely-failed run reaches One subtlety to be aware of
I traced it and the outcome is still correct: every fatal continuation re-publishes Also confirmed Upstream
Opening the upstream PR against |
|
Superseding this with #125, which fixes the same bug at the producer. Full reasoning there; the short version of why we changed course after the earlier review approved this: Your diagnosis was correct and your test caught the real regression — both are carried forward into #125 (fixture credited). What changed our mind is that the CLI is one of four consumers of Looking at the producer instead, the publish at That makes every consumer correct as written: your One thing your PR got right that we kept as a hard requirement: the failed-recovery case must still exit 1. That only worked pre-#125 by accident (the halt re-entry during summary published the event), so #125 adds an explicit test pinning exit 1 with exactly one error event when compaction can't shrink the session. The same patch is going upstream to anomalyco/opencode, where this bug exists verbatim — your consumer-side approach remains the right fallback there if they insist on keeping the blip event. Thanks for the report and the fixture; this one was a real production bug with a clean diagnosis. |
… attempted
A provider 413 with auto-compaction on published Session.Event.Error and
then recovered: compact, retry, succeed. Every consumer of that channel
treats it as the run's outcome — CLI run exits 1, TUI shows an error
toast, the desktop app fires an OS notification, and orchestrators
tailing --format json mark the run failed — so a run that produced a
complete, correct result was reported as a failure.
The publish at processor.halt's auto-compact branch was the outlier on
three counts: it was the only publisher that does not set message.error
(the durable record disagreed with the event), the only one not followed
by idle or a throw, and nothing consumed it (compaction is triggered by
the "compact" return value). Recoverable provider errors elsewhere are
silent: retried 429s never publish, and the proactive overflow check
sets needsCompaction without an event.
Move the announcement to where failure is actually decided: compaction's
result === "compact" branch ("too large to compact even after
stripping"), which already writes the durable error but published
nothing — its error event previously arrived only as a side effect of
the halt re-entering during summary generation. Net contract:
session.error with a sessionID fires iff the outcome is affected.
Tests pin both directions: 413 -> compact -> retry exits 0 with no error
event; 413 -> compaction also overflows exits 1 with exactly one error
event carrying the clearer compaction message. Both fail without the
source change (verified by stashing it).
The recovered-413 fixture is from browser-use#123 by Magnus, which fixed the same
symptom consumer-side in run.ts; this supersedes it at the producer so
run.ts stays identical to upstream and stream consumers need no
clear-on-later-success logic.
Summary
Test plan
bun test test/cli/run/run-process.test.ts --test-name-pattern "exits 0 when compaction recovers a provider size error"bun typecheckfrompackages/opencodeSummary by cubic
Fixes CLI run error recovery so a recovered provider error no longer forces a failure. Non-interactive runs now exit 0 after automatic compaction successfully recovers from a size error.
finishand noerror.Written for commit 066b4b8. Summary will update on new commits.