{"record":{"id":"b62ba705c723c90d","repo":"can1357/oh-my-pi","slug":"anthropicconnectionerror","errorCode":null,"errorMessage":"AnthropicConnectionError","messagePattern":"AnthropicConnectionError","errorType":"exception","errorClass":"AnthropicConnectionError","httpStatus":null,"severity":"error","filePath":"packages/ai/src/providers/anthropic-client.ts","lineNumber":245,"sourceCode":"\t\tconst maxRetryDelayMs = options?.maxRetryDelayMs ?? opts.maxRetryDelayMs ?? 60_000;\n\t\tconst url = `${opts.baseURL ?? \"https://api.anthropic.com\"}${path}`;\n\t\tconst headers = this.#buildHeaders(options?.headers);\n\t\tconst body = JSON.stringify(params);\n\n\t\tfor (let attempt = 0; ; attempt++) {\n\t\t\tif (callerSignal?.aborted) throw createAbortError();\n\n\t\t\tlet response: Response;\n\t\t\ttry {\n\t\t\t\tresponse = await this.#fetchOnce(fetchFn, url, headers, body, timeoutMs, callerSignal);\n\t\t\t} catch (error) {\n\t\t\t\tif (callerSignal?.aborted) throw createAbortError();\n\t\t\t\tif (attempt < maxRetries) {\n\t\t\t\t\tawait this.#backoff(attempt, undefined, callerSignal);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tif (error instanceof AIError.AnthropicConnectionTimeoutError) throw error;\n\t\t\t\tthrow new AIError.AnthropicConnectionError(error);\n\t\t\t}\n\n\t\t\tif (response.ok) return response;\n\n\t\t\tif (attempt < maxRetries && shouldRetryResponse(response)) {\n\t\t\t\t// Bound the server-directed wait: an over-cap `retry-after` declines\n\t\t\t\t// the retry and surfaces the original error (status/body/headers\n\t\t\t\t// intact) so higher-level recovery can run. A non-positive cap disables enforcement.\n\t\t\t\t// Checked before draining the body so `fromResponse` can still read it.\n\t\t\t\tconst headerDelayMs = retryDelayFromHeaders(response.headers);\n\t\t\t\tif (headerDelayMs !== undefined && maxRetryDelayMs > 0 && headerDelayMs > maxRetryDelayMs) {\n\t\t\t\t\tthrow await AIError.AnthropicApiError.fromResponse(response, callerSignal);\n\t\t\t\t}\n\t\t\t\tawait response.body?.cancel().catch(() => {});\n\t\t\t\tawait this.#backoff(attempt, response.headers, callerSignal);\n\t\t\t\tcontinue;\n\t\t\t}\n","sourceCodeStart":227,"sourceCodeEnd":263,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/providers/anthropic-client.ts#L227-L263","documentation":"AnthropicClient.#send retries fetch-level failures (network errors) up to maxRetries with backoff. If the final attempt still throws a low-level network error (and it is not already a connection-timeout error), it is wrapped in AnthropicConnectionError. This means the client could not establish or complete the HTTP connection to the Anthropic API at all — DNS failure, connection refused/reset, TLS error, or socket hang-up.","triggerScenarios":"fetch() throwing on any attempt of a request() call and retries exhausted: DNS resolution failure, ECONNREFUSED/ECONNRESET, TLS handshake failure, proxy misconfiguration, or the machine being offline.","commonSituations":"Corporate proxies/firewalls blocking api.anthropic.com; ANTHROPIC_BASE_URL pointing at a wrong port or a down local gateway; VPN required but not connected; IPv6/DNS issues in containers; transient network blips exceeding the retry budget.","solutions":["Check basic connectivity to the API endpoint (curl -v https://api.anthropic.com) and fix network/VPN/proxy issues","Verify ANTHROPIC_BASE_URL (or configured baseURL) — wrong host, port, or scheme produces exactly this wrapped failure","Inspect the wrapped `cause` of AnthropicConnectionError for the underlying errno (ENOTFOUND, ECONNREFUSED, etc.) to target the fix","Increase maxRetries if failures are transient (flaky networks), and add application-level backoff for long outages","If behind a corporate proxy, configure HTTPS_PROXY and the agent options the client accepts"],"exampleFix":"// before: no diagnostics, fails in prod VPC\nconst client = new AnthropicClient(apiKey);\n// after: explicit endpoint + cause logging\ntry {\n  await client.request(...);\n} catch (e) {\n  if (e instanceof AIError.AnthropicConnectionError) {\n    logger.error(\"anthropic unreachable\", { cause: e.cause?.message, baseURL });\n  }\n  throw e;\n}","handlingStrategy":"retry","validationCode":"// pre-flight endpoint sanity check\nconst url = new URL(baseURL ?? \"https://api.anthropic.com\");\nif (url.protocol !== \"https:\" && process.env.NODE_ENV === \"production\") {\n  throw new Error(`Anthropic baseURL must be https in production: ${url}`);\n}","typeGuard":"import { AIError } from \"@oh-my-pi/pi-ai\";\nfunction isAnthropicConnectionError(e: unknown): e is AIError.AnthropicConnectionError {\n  return e instanceof AIError.AnthropicConnectionError;\n}","tryCatchPattern":"try {\n  return await client.request(req);\n} catch (e) {\n  if (isAnthropicConnectionError(e)) {\n    // network-level failure after retries; log cause (ENOTFOUND/ECONNREFUSED/TLS) and back off\n    logger.error(\"anthropic unreachable\", { cause: (e.cause as Error)?.message });\n    await Bun.sleep(retryDelay);\n    return client.request(req); // or surface a user-facing outage error\n  }\n  throw e;\n}","preventionTips":["Verify base URL, DNS, and proxy/VPN reachability in CI health checks before deploys","Log the wrapped cause errno to distinguish DNS vs refused vs TLS failures","Keep client maxRetries modest and add circuit breaking for sustained outages","Configure HTTPS_PROXY/agent settings explicitly in corporate networks"],"tags":["network","anthropic","connection","fetch"],"backgroundTag":"connection-refused","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}