"Request timed out." is the literal default message of the Anthropic and OpenAI
SDKs' APIConnectionTimeoutError, surfaced to Fusion by checkSessionError after
pi-coding-agent exhausts its in-session retries. It matched none of the
connection-scoped timeout patterns, which deliberately excluded "general
timeouts", so it fell through to specifyTask's generic failure branch — the one
that restores status: null and writes no counter, no nextRecoveryAt, and no park.
Triage rediscovery then re-admitted the card on the very next poll, forever.
Measured before this change: 48 "Specification failed: Request timed out." events
across 10 tasks in 30 hours with zero backoff between attempts. FN-8950 alone
burned 8 consecutive attempts over ~8 hours and never reached implementation.
Across 2 days, 91 failed planning attempts averaged 33 minutes each — ~50 hours of
wall-clock producing nothing, 24% of all planning time.
Classifying these as transient routes them into the bounded recovery policy
(MAX_RECOVERY_RETRIES = 3, 60s/120s/300s jittered backoff) already used by the
connection-level patterns, so a provider blip costs three spaced retries instead
of an unbounded loop.
The pattern is anchored to "request timed out" rather than a bare timeout match:
agent log prose and verification output legitimately contain "timed out"
("BuildKit timed out", "stuck-kill unwind timeout"), and a broad pattern would
reclassify real permanent failures as retryable — the mistake the connection-only
rule was written to avoid. Regression tests pin both directions.
This does not affect model fallback, which pi decides internally and Fusion only
observes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
968 B
@runfusion/fusion
| @runfusion/fusion |
|---|
| patch |
summary: Provider request timeouts now retry with backoff instead of looping planning forever.
category: fix
dev: Adds /\brequest timed out\b/i and /\bAPIConnectionTimeoutError\b/i to TRANSIENT_ERROR_PATTERNS (packages/engine/src/errors/transient-error-patterns.ts). The Anthropic/OpenAI SDK APIConnectionTimeoutError default message "Request timed out." matched no pattern, so it fell through to specifyTask's generic failure branch which restores status: null with no counter and no nextRecoveryAt — triage rediscovery then re-admitted the card every poll indefinitely. Now routes into the bounded MAX_RECOVERY_RETRIES (3) + 60s/120s/300s backoff policy. Pattern is anchored to "request timed out" rather than a bare timeout match so agent prose and verification output ("BuildKit timed out", "Test suite timed out after 30000ms") stay permanent. Does not affect model fallback, which pi decides internally.