fix(agents): classify timeout-triggered claude aborts as agent_timeout - #26
Merged
Merged
Conversation
When a stage exceeds timeoutMs, the timer calls controller.abort() and the
Claude Agent SDK throws a generic "Claude Code process aborted by user"
error with no way to distinguish a wall-clock timeout from a caller abort.
That raw error escaped the event loop unwrapped, so reportFailure bucketed
it as the catch-all "internal" kind, masking the real cause.
Track whether our own timer fired the abort and reclassify the thrown error
as AgentError("agent_timeout") in the event-loop catch. External aborts and
intentional AgentErrors pass through unchanged. This mirrors the pattern
already present in codex.ts.
Contributor
There was a problem hiding this comment.
Shopfloor agent review: clean across 4/4 reviewers.
- No compliance violations: the dist bundle was rebuilt per the committed-bundle rule, file placement matches existing structure, and the change follows the documented AgentError timeout pattern.
- The timeout-reclassification logic is correctly implemented and matches the AgentError constructor and codex pattern; both new tests genuinely exercise the timeout and external-abort paths.
- Reviewed the timeout-reclassification diff in src/agents/claude.ts and its tests; the change only adjusts error handling and introduces no exploitable security issues.
- Inspected the diff for src/agents/claude.ts and test/agents/claude.test.ts (excluding the generated dist bundle); the timeout-reclassification change is minimal, well-named, and free of duplication or dead code.
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.
Problem
When a stage exceeds its
timeoutMs, the timeout timer inClaudeAgentAdaptercallscontroller.abort(). The Claude Agent SDK responds by throwing a genericError("Claude Code process aborted by user")with no way to distinguish a wall-clock timeout from a deliberate caller-initiated abort.That raw error escaped the agent event loop unwrapped, so
reportFailure(src/orchestrator.ts) fell through to the catch-allinternalkind. Failures surfaced to users as:which is misleading — nothing was aborted by a user, the stage simply ran past its timeout. Observed on a real triage run that was actively mid-investigation when the 5-minute timeout fired.
Fix
timedOutflag.catch, reclassify a timed-out raw error asAgentError("agent_timeout", ...).AbortController) and intentionalAgentErrors pass through unchanged.This mirrors the pattern already present in
src/agents/codex.ts, which classified timeouts correctly; only the Claude path was missing it.Tests
Two new cases in
test/agents/claude.test.ts:AgentError(agent_timeout)Full suite: 288 passing.
typecheckclean,lintexit 0,dist/index.cjsrebuilt.