{"record":{"id":"f06b78f13b75a730","repo":"can1357/oh-my-pi","slug":"anthropicconnectiontimeouterror","errorCode":null,"errorMessage":"AnthropicConnectionTimeoutError","messagePattern":"AnthropicConnectionTimeoutError","errorType":"exception","errorClass":"AnthropicConnectionTimeoutError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/providers/anthropic-client.ts","lineNumber":293,"sourceCode":"\t): Promise<Response> {\n\t\tconst controller = new AbortController();\n\t\tlet timedOut = false;\n\t\tconst timer = setTimeout(() => {\n\t\t\ttimedOut = true;\n\t\t\tcontroller.abort();\n\t\t}, timeoutMs);\n\t\tconst onAbort = () => controller.abort();\n\t\tcallerSignal?.addEventListener(\"abort\", onAbort, { once: true });\n\t\ttry {\n\t\t\treturn await fetchFn(url, {\n\t\t\t\t...(this.#options.fetchOptions ?? {}),\n\t\t\t\tmethod: \"POST\",\n\t\t\t\theaders,\n\t\t\t\tbody,\n\t\t\t\tsignal: controller.signal,\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tif (timedOut && !callerSignal?.aborted) throw new AIError.AnthropicConnectionTimeoutError();\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tclearTimeout(timer);\n\t\t\tcallerSignal?.removeEventListener(\"abort\", onAbort);\n\t\t}\n\t}\n\n\tasync #backoff(\n\t\tattempt: number,\n\t\tresponseHeaders: Headers | undefined,\n\t\tsignal: AbortSignal | undefined,\n\t): Promise<void> {\n\t\tconst delayMs = retryDelayFromHeaders(responseHeaders) ?? calculateAnthropicRetryDelayMs(attempt);\n\t\ttry {\n\t\t\tawait scheduler.wait(delayMs, { signal });\n\t\t} catch {\n\t\t\tthrow createAbortError();\n\t\t}","sourceCodeStart":275,"sourceCodeEnd":311,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/providers/anthropic-client.ts#L275-L311","documentation":"AnthropicClient.#fetchOnce sets a timeout timer around its fetch call, aborting the request controller when it fires. If the fetch rejects due to that internal timeout (timedOut flag set) while the caller's own signal was NOT aborted, the client throws AnthropicConnectionTimeoutError. This distinguishes 'the API did not respond within the client's timeout window' from a caller-initiated cancellation.","triggerScenarios":"A request to the Anthropic API exceeds the client's configured timeout: slow/hung TLS negotiation, no response headers from a stalled proxy, or an extremely slow network — after which the internal AbortController fires and fetch rejects with an abort error attributed to the timer.","commonSituations":"Long non-streaming requests (large prompts) exceeding a tight timeout; requests routed through a hanging corporate proxy; regional network congestion; misconfigured timeout for batch/offline workloads that legitimately take minutes.","solutions":["Increase the client's request timeout option to accommodate your largest non-streaming requests","Use streaming responses so connection/TTFT timeouts don't fire while tokens are still expected","Retry with backoff — these are transient by nature; combine with a circuit breaker for persistent stalls","Check whether a proxy/LB in the path silently drops long-lived connections and raise its idle timeout","Distinguish AnthropicConnectionTimeoutError from AbortError in your catch handling so caller cancellations aren't retried"],"exampleFix":"// before: default timeout too small for big prompts\nconst client = new AnthropicClient(key);\n// after: explicit timeout + retry on timeout only\ntry {\n  return await client.request(req, { timeoutMs: 120_000 });\n} catch (e) {\n  if (e instanceof AIError.AnthropicConnectionTimeoutError) return retryWithBackoff(req);\n  throw e;\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"import { AIError } from \"@oh-my-pi/pi-ai\";\nfunction isConnectionTimeout(e: unknown): e is AIError.AnthropicConnectionTimeoutError {\n  return e instanceof AIError.AnthropicConnectionTimeoutError;\n}","tryCatchPattern":"try {\n  return await client.request(req);\n} catch (e) {\n  if (isConnectionTimeout(e)) {\n    // server never responded in time — safe to retry with backoff\n    return retryWithBackoff(() => client.request(req), { attempts: 3 });\n  }\n  throw e; // AbortError (caller cancelled) and others must not be retried\n}","preventionTips":["Size the client timeout to your largest non-streaming request (or use streaming)","Always distinguish timeout from caller AbortError before retrying","Alert on timeout rates — a rising trend usually means proxy/LB idle-timeout problems","Keep retry counts bounded and add jitter to avoid thundering herds"],"tags":["network","anthropic","timeout","fetch"],"backgroundTag":"request-timeout","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}