can1357/oh-my-pi · warning · AIError.StreamTimeoutError

options.firstItemErrorMessage ?? options.errorMessage

Error message

options.firstItemErrorMessage ?? options.errorMessage

What it means

AIError.StreamTimeoutError thrown by iterateWithIdleTimeout when no first item arrives within options.firstItemDeadlineMs and there is no pending local work. The message comes from options.firstItemErrorMessage ?? options.errorMessage, so callers control the surfaced text. It distinguishes 'provider never started responding' from mid-stream stalls.

Source

Thrown at packages/ai/src/utils/idle-iterator.ts:337

					const { promise, resolve } = Promise.withResolvers<{ kind: "abort" }>();
					resolveAbort = resolve;
					abortPromise = promise;
				}
				if (timeoutPromise !== undefined && !timeoutFired) {
					const { promise, resolve } = Promise.withResolvers<{ kind: "timeout" }>();
					resolveTimeout = resolve;
					timeoutPromise = promise;
				}
			}
			let activeTimeoutMs: number | undefined;
			if (awaitingFirstItem) {
				if (firstItemDeadlineMs !== undefined) {
					activeTimeoutMs = firstItemDeadlineMs - Date.now();
					if (activeTimeoutMs <= 0) {
						if (!hasPendingLocalWork()) {
							options.onFirstItemTimeout?.();
							closeIterator();
							throw new AIError.StreamTimeoutError(options.firstItemErrorMessage ?? options.errorMessage);
						}
						extendDeadlineForLocalWork();
						activeTimeoutMs = firstItemDeadlineMs! - Date.now();
					}
				}
			} else if (options.idleTimeoutMs !== undefined && options.idleTimeoutMs > 0) {
				activeTimeoutMs = options.idleTimeoutMs - (Date.now() - lastProgressAt);
				if (activeTimeoutMs <= 0) {
					if (!hasPendingLocalWork()) {
						options.onIdle?.();
						closeIterator();
						throw new AIError.StreamTimeoutError(options.errorMessage);
					}
					extendDeadlineForLocalWork();
					activeTimeoutMs = options.idleTimeoutMs;
				}
			}

View on GitHub (pinned to 9690622007)

Solutions

  1. Increase firstItemDeadlineMs (or use a smaller firstItemErrorMessage-informing deadline) to accommodate model latency.
  2. Set a descriptive options.firstItemErrorMessage so downstream handlers can distinguish start-timeout from idle-timeout.
  3. Use options.onFirstItemTimeout to implement fallback (e.g. retry against a different provider/region).
  4. Check provider status/network path — persistent first-item timeouts indicate connectivity or provider issues, not tuning.

Example fix

// before
iterateWithIdleTimeout(stream, { firstItemDeadlineMs: 2_000, errorMessage: "stream timeout" });
// after
iterateWithIdleTimeout(stream, {
	firstItemDeadlineMs: 30_000,
	onFirstItemTimeout: () => scheduleFallback(),
	firstItemErrorMessage: "no first token within 30s",
	errorMessage: "stream idle timeout",
});
Defensive patterns

Strategy: retry

Try / catch

try {
	for await (const ev of timedStream) handle(ev);
} catch (err) {
	if (err instanceof AIError.StreamTimeoutError) {
		// first-item timeout: retry once, then fall back to another provider
	} else throw err;
}

Prevention

When it happens

Trigger: Any stream wrapped with iterateWithIdleTimeout (timedAnthropicStream, timedOpenaiStream, chunks, wrapCodexSseStream, watchedSource, run) where the first chunk/association event does not arrive before firstItemDeadlineMs elapses — e.g. slow model time-to-first-token, hung TLS handshake, or a blocked proxy.

Common situations: Provider outage or severe latency making time-to-first-token exceed the deadline; network firewall silently dropping the connection; first-item deadline configured too aggressively for a slow/large-prompt request.

Related errors


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