Skip to content

fix(agents): classify timeout-triggered claude aborts as agent_timeout - #26

Merged
niranjan94 merged 1 commit into
release/v2from
fix/claude-timeout-classification
Jun 22, 2026
Merged

fix(agents): classify timeout-triggered claude aborts as agent_timeout#26
niranjan94 merged 1 commit into
release/v2from
fix/claude-timeout-classification

Conversation

@niranjan94

Copy link
Copy Markdown
Owner

Problem

When a stage exceeds its timeoutMs, the timeout timer in ClaudeAgentAdapter calls controller.abort(). The Claude Agent SDK responds by throwing a generic Error("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-all internal kind. Failures surfaced to users as:

**Kind:** `internal`
Claude Code process aborted by user

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

  • Track whether our timeout timer fired the abort via a timedOut flag.
  • In the event-loop catch, reclassify a timed-out raw error as AgentError("agent_timeout", ...).
  • External aborts (caller-supplied AbortController) and intentional AgentErrors 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:

  • timeout-triggered abort → AgentError(agent_timeout)
  • externally-triggered abort → not reclassified (passes through as a raw error)

Full suite: 288 passing. typecheck clean, lint exit 0, dist/index.cjs rebuilt.

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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@niranjan94
niranjan94 merged commit 8271b73 into release/v2 Jun 22, 2026
6 checks passed
@niranjan94
niranjan94 deleted the fix/claude-timeout-classification branch June 22, 2026 12:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant