can1357/oh-my-pi · error · AnthropicConnectionTimeoutError

AnthropicConnectionTimeoutError

Error message

AnthropicConnectionTimeoutError

What it means

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.

Source

Thrown at packages/ai/src/providers/anthropic-client.ts:293

	): Promise<Response> {
		const controller = new AbortController();
		let timedOut = false;
		const timer = setTimeout(() => {
			timedOut = true;
			controller.abort();
		}, timeoutMs);
		const onAbort = () => controller.abort();
		callerSignal?.addEventListener("abort", onAbort, { once: true });
		try {
			return await fetchFn(url, {
				...(this.#options.fetchOptions ?? {}),
				method: "POST",
				headers,
				body,
				signal: controller.signal,
			});
		} catch (error) {
			if (timedOut && !callerSignal?.aborted) throw new AIError.AnthropicConnectionTimeoutError();
			throw error;
		} finally {
			clearTimeout(timer);
			callerSignal?.removeEventListener("abort", onAbort);
		}
	}

	async #backoff(
		attempt: number,
		responseHeaders: Headers | undefined,
		signal: AbortSignal | undefined,
	): Promise<void> {
		const delayMs = retryDelayFromHeaders(responseHeaders) ?? calculateAnthropicRetryDelayMs(attempt);
		try {
			await scheduler.wait(delayMs, { signal });
		} catch {
			throw createAbortError();
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Increase the client's request timeout option to accommodate your largest non-streaming requests
  2. Use streaming responses so connection/TTFT timeouts don't fire while tokens are still expected
  3. Retry with backoff — these are transient by nature; combine with a circuit breaker for persistent stalls
  4. Check whether a proxy/LB in the path silently drops long-lived connections and raise its idle timeout
  5. Distinguish AnthropicConnectionTimeoutError from AbortError in your catch handling so caller cancellations aren't retried

Example fix

// before: default timeout too small for big prompts
const client = new AnthropicClient(key);
// after: explicit timeout + retry on timeout only
try {
  return await client.request(req, { timeoutMs: 120_000 });
} catch (e) {
  if (e instanceof AIError.AnthropicConnectionTimeoutError) return retryWithBackoff(req);
  throw e;
}
Defensive patterns

Strategy: retry

Type guard

import { AIError } from "@oh-my-pi/pi-ai";
function isConnectionTimeout(e: unknown): e is AIError.AnthropicConnectionTimeoutError {
  return e instanceof AIError.AnthropicConnectionTimeoutError;
}

Try / catch

try {
  return await client.request(req);
} catch (e) {
  if (isConnectionTimeout(e)) {
    // server never responded in time — safe to retry with backoff
    return retryWithBackoff(() => client.request(req), { attempts: 3 });
  }
  throw e; // AbortError (caller cancelled) and others must not be retried
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/f06b78f13b75a730. Report an issue: GitHub.