fix(orchestration): a child whose provider turn failed reports failed (#1018) - #1044
Open
Juliusolsson05 wants to merge 6 commits into
Open
Juliusolsson05 wants to merge 6 commits into
Juliusolsson05 wants to merge 6 commits into
Conversation
…1018) The instrumentation stage. A catalog from real feed-debug recordings, OpenCode databases, Codex rollouts and Claude transcripts on this machine: 27 recorded api_errors across OpenCode structured, OpenCode Terminal and Codex, and Claude's committed error entries. For each, the sequence around the error: retries, process state, idle, and turn end. It gives a precise terminal-failure rule in runtime fields, with its evidence and unknowns, plus six sanitized fixture sequences with provenance. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nces (#1018) The recorded feed-debug streams replayed through the renderer's real semantic fold: - OpenCode structured and Terminal usage limits end failed with the provider's text; - a retry still in flight is never failed; - Codex's error, which arrives while the process is still active, is failed only once idle; - Claude's committed limit entry is a failure, not an answer, and a later real answer beats it. In main, a failed child the parent prompts again reads prompt_sent. Each case fails with its branch removed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…#1018) A child whose provider failed before any output (a usage limit, an auth rejection) read waiting, and main turned that into prompt_sent forever. Parents burned wait timeouts, and six children in one run stalled at once. terminalProviderFailure implements the catalog's rule: - the failure is a semantic api_error (OpenCode, Codex, Grok), or for Claude the committed isApiErrorMessage entry, because Claude retries on its own; - the provider is idle. Errors land before idle in 27 of 27 recordings, so no timer is needed; - the failure is newer, by producer time, than the latest real output. The lifecycle checks it after running and before completed, so a Claude limit no longer reads as the child's answer. The record carries errorSummary and a new failedAt, and main reports prompt_sent when the parent prompts the failed child again. orchestration_wait_agents already treats failed as done. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Only a dispatcher lookup change and its test, so package.json and the lockfile are unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nd instance errors never do Review of #1044 (CHANGES REQUESTED): 1. Grok regression, reproduced by the reviewer. Grok transcript rows carry no timestamp, so an old api_error was always newer than the latest output. A child whose turn 1 failed and whose turn 2 answered read failed forever, and main turned that into prompt_sent, which is #1018 again. The signal is back to the catalog's §3.1 rule: OpenCode from opencode-sse, Codex from proxy, Claude's committed entry, and nothing for Grok, which has no recording to build a rule from. A real assistant row with an unparseable timestamp now answers "not failed". 2. Non-terminal OpenCode errors: - session-less instance errors from a broken SKILL.md, agent, command or plugin file; - the delta-buffer overflow; - a user's Esc (MessageAbortedError). Each is now named in errorType by the packages (opencode-headless#16, opencode-terminal-headless#4, both bumped here), and none makes a child failed. They still show in the feed. 3. Tests for the cases that must NOT fail: - Grok, through the real Grok mapper; - an instance error after the final answer; - the recorded Esc (case b); - the recorded 403 then user re-prompt (case c): failed while idle, running once the new turn starts; - Codex recovery. The first three fail with the rule change reverted. The retry-in- flight test is now labelled a guard; it passed before #1018 too. 4. idleOrchestrationAgents' WHY claimed Dispatch paints failed workers with an error. It paints runtime state, so a usage-limited worker can look idle there; the comment now says why excluding it matters. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ule; pin the fail-open clause Delta re-review of #1044 (APPROVE) left two items, both fixed here: - nit 2: terminalProviderFailure read meta?.kind with no default. A Claude child whose meta has no kind therefore had no failure signal, while lifecycleStateForRuntime already defaults to DEFAULT_PROVIDER. The test fails without the default. - finding 1: the fail-open clause had no test. An assistant row whose time cannot be read now pins "not failed", and the same runtime without that row still fails. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Juliusolsson05
added a commit
that referenced
this pull request
Sep 19, 2026
…1047 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #1018.
Why
An orchestration child whose provider failed before any output reported
prompt_sent/waitingforever.waiting, and main's prompt overlay turned that intoprompt_sent.orchestration_wait_agentsstill showed every one asprompt_sentwith noerrorSummary.Staged, evidence first
testing/fixtures/orchestration-api-error/). This covers 27 realapi_errors: OpenCode structured, OpenCode Terminal and Codex, from feed-debug, the OpenCode databases and Codex rollouts. It also covers Claude's committed error entries. Each is catalogued with its sequence around the error, and there are 6 sanitized fixtures with provenance. The key facts:api_errorwhile it retries (processActivestays true, 10/10).api_erroris not final (Claude retries); its committedisApiErrorMessageentry is.terminalProviderFailureinorchestrationMcp.ts): there is a failure signal, the provider is idle, and the failure is newer, by producer time, than the latest real output. The lifecycle checks it afterrunningand beforecompleted, so a Claude limit no longer reads as the child's answer.errorSummaryplus a newfailedAt. Main's overlay reportsprompt_sentwhen the parent prompts the failed child again.orchestration_wait_agentsalready treatsfailedas done.error.data.message, so the parent sees "Usage limit reached for 5 hour. Your limit will reset at …".Tests
orchestrationFailure.test.tsreplays the recorded sequences through the real semantic fold:failedwith the provider's text;failed;failedonly once idle;OrchestrationBridge.test.ts: a failed child that is prompted again readsprompt_sent.Known gaps (from the catalog)
error.nameorstatusCode, so a user abort also readsfailedwith the message "Aborted". That's correct for coordination, because the child will not continue on its own, but it has no label.🤖 Generated with Claude Code