[FEATURE] Failure classification taxonomy for headless/autonomous sessions

Resolved 💬 3 comments Opened Mar 12, 2026 by stackbilt-admin Closed Mar 16, 2026

Problem

When running Claude Code autonomously via claude -p, there's no way to programmatically determine why a session ended. Exit code 0 covers: successful completion, max turns exhaustion, context window full, model self-terminated, and blocked-by-hook. Exit code 1 covers: API auth failure, rate limit, tool crash, and MCP server unavailable. Orchestrators need to handle these differently (retry vs escalate vs skip).

Real-world context

We operate a production task queue that executes 50+ autonomous Claude Code sessions per week via claude -p. Over ~1,000 completed tasks, we identified 7 distinct failure modes that require different handling:

| Failure Kind | Description | Correct Response |
|-------------|-------------|-----------------|
| timeout | Max turns reached, task incomplete | Retry with higher --max-turns |
| context_exhausted | Context window full mid-task | Split task into smaller units |
| hook_blocked | Safety hook blocked a required action | Escalate to operator |
| auth_failure | API key expired or rate limited | Retry after backoff |
| tool_error | Tool crashed (MCP server down, git conflict) | Retry once, then escalate |
| model_refusal | Model declined to proceed (safety, confusion) | Rewrite prompt, escalate |
| unknown | Exit 0 but no completion signal found | Flag for manual review |

Currently we detect these by grepping stdout for convention-based strings (TASK_COMPLETE, TASK_BLOCKED: <reason>) and parsing error messages — which is fragile and lossy.

Proposed solution

Structured termination metadata in --output-format json:

{
  "result": "...",
  "termination": {
    "reason": "max_turns_reached",
    "turns_used": 50,
    "turns_max": 50,
    "context_tokens_used": 195000,
    "context_tokens_max": 200000,
    "blocked_tool": null,
    "error": null
  }
}

And/or distinct exit codes:

| Code | Reason |
|------|--------|
| 0 | Completed — model indicated task finished |
| 10 | Max turns reached |
| 11 | Context exhausted |
| 12 | Blocked by hook — couldn't proceed |
| 13 | Idle — model stopped without explicit completion |
| 1 | Error (API, auth, tool crash) |

Why this matters

Every team building autonomous Claude Code workflows (CI/CD pipelines, task runners, agent frameworks) needs failure classification to implement correct retry/escalate/skip logic. Without it, the only option is string-matching on stdout — which breaks across versions and can't distinguish between failure modes that require fundamentally different responses.

Our production circuit breaker (3 consecutive failures → pause queue) would be more precise with structured termination reasons — right now it treats all failures equally, which means a rate limit (transient) triggers the same response as a model refusal (permanent).

Related issues

  • #32620 — Structured completion signals (our prior issue, subset of this)
  • #27244 — Hook exit code for session termination
  • #27172 — Unattended fail-fast mode

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗