Workflow tool: let a script read why agent() returned null (rate_limit vs authentication_failed vs server_error)
Summary
Inside a Workflow script, agent() resolves to null when a subagent dies on a terminal API error, and the script cannot tell why it died. The runtime already knows the cause and writes it to disk, but the script has no filesystem access, so it cannot read it. Please surface the terminal error type to the script.
Why a script needs the cause
A script deciding how to retry a dead agent needs the cause, because each cause has a different correct response:
| Cause | Correct response |
|---|---|
| rate_limit (session or weekly limit) | Do not retry now. Every retry on the same account fails the same way until the reset time. |
| authentication_failed ("Login expired") | Do not retry. Nothing succeeds until someone logs in. |
| server_error 529 (overloaded) | Retry after a short delay. |
| A dead or unusable return on a small model | Retry once on a larger model. |
Without the cause, a script falls back to a heuristic. Ours looks at the share of a fan-out wave that died. Above a threshold, it assumes a usage limit and retries on the same model. Below it, it assumes an isolated failure and retries on a larger model. That guess is wrong in both directions:
- one isolated agent can hit a usage limit and get a pointless retry on a larger model;
- a whole wave can die of an expired login, which no retry fixes.
Evidence that the runtime has the cause
Each dead agent's transcript (~/.claude/projects/<project>/<session>/subagents/workflows/wf_*/agent-*.jsonl) ends with a row carrying isApiErrorMessage: true plus typed error and apiErrorStatus fields. On one machine, 21,583 workflow agent transcripts hold 1,109 such rows. The commonest are:
error: "rate_limit", apiErrorStatus: 429, text: "You've hit your session limit · resets 12pm (America/New_York)"
error: "authentication_failed", text: "Login expired · Please run /login"
Main-session transcripts also show error: "server_error" with status 529 ("Overloaded"). The per-run state file (workflows/wf_*.json, workflowProgress[].error) records the same message per agent, as #93728 describes.
Requested change
Let a script read the terminal cause of a dead agent. Two shapes would work:
- A typed rejection, opt-in. For example
agent(prompt, { onError: 'throw' })rejects with an error carrying{ cause: 'rate_limit' | 'authentication_failed' | 'server_error' | 'other', status, resetsAt? }. The default behavior, resolving tonull, stays unchanged for existing scripts. - A side channel. For example
agentError(handle)orlastAgentError(label), returning the same object afteragent()resolves tonull.
resetsAt, parsed from the limit message the runtime already prints, would let a script stop cleanly and report when to resume, rather than guess.
Related
- #93728: the dispatcher keeps launching queued agents after a rate-limit stop. That one asks for a circuit breaker in the runtime; this one asks for the cause to reach the script. They complement each other.
Environment
Claude Code 2.1.293, Linux.