feat: say when a step was stopped by the model provider - #687
Merged
Conversation
A step killed by a provider error parked with "Waiting for user input" — a
message that reads as a question the agent never asked. The real cause was
recorded only in the session transcript, so on the board a rate-limited step was
indistinguishable from one genuinely waiting on a human.
That cost a real debugging session: a workflow step ran three times, committed
nothing each time, and looked like a bad prompt or a broken worktree. The
transcript said
API Error: Request rejected (429) · you have reached your session usage
limit, upgrade for higher limits: https://ollama.com/upgrade
LastAPIError reads the transcript Claude Code already hands the hook
(transcript_path) and reports the failure a session ENDED on, keyed off the
entry's own isApiErrorMessage/apiErrorStatus/apiErrorIsTransient fields rather
than sniffing text. Errors the session recovered from are ignored — Claude Code
retries transient failures, and reporting those would train the reader to skip
these lines. When the provider says a failure is transient, the log says a retry
may work.
Wired into all three places a task parks: the Notification hook's idle and
permission prompts, and the Stop hook's workflow-step and generic end_turn paths.
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.
Why
Follow-up to #686. While recovering pipeline #5120, one step ran three times, committed nothing each time, and looked like a bad prompt or a broken worktree. Its activity log said only:
The actual cause existed only inside the session transcript:
On the board, a rate-limited step is indistinguishable from one genuinely waiting on a human — and "Waiting for user input" actively misleads, since it reads as a question the agent never asked.
What
executor.LastAPIErrorreads the transcript Claude Code already hands the hook (transcript_path) and reports the provider failure a session ended on. It keys off the transcript entry's own fields —isApiErrorMessage,apiErrorStatus,apiErrorIsTransient— rather than sniffing message text, so it doesn't depend on error wording.Errors the session recovered from are ignored: Claude Code retries transient failures, and reporting those would train the reader to skip these lines. When the provider itself says a failure is transient, the log line says a retry may work.
Wired into all three places a task parks:
end_turnpathThe board now shows, as an
errorline:Verification
Run against the real transcript from the incident:
Unit tests cover: the failure a session ended on, a failure it recovered from (must stay silent), the transient hint, a clean session, and a missing/empty transcript path.
go test ./...— all greengo vet ./...,gofmt -l— cleangolangci-lint runwith the CI-pinned v2.8.0 — 0 issuesNo visible surface changes beyond the log line quoted above, which is text on the existing activity feed.
🤖 Generated with Claude Code