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
- Increase firstItemDeadlineMs (or use a smaller firstItemErrorMessage-informing deadline) to accommodate model latency.
- Set a descriptive options.firstItemErrorMessage so downstream handlers can distinguish start-timeout from idle-timeout.
- Use options.onFirstItemTimeout to implement fallback (e.g. retry against a different provider/region).
- 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
- Set firstItemDeadlineMs well above observed p99 time-to-first-token.
- Wire onFirstItemTimeout to a fallback provider/region.
- Monitor TTFT metrics and adjust the deadline from data.
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
- options.errorMessage
- timed out: {command}
- V2 compaction stream closed before response.completed
- AnthropicConnectionTimeoutError
- stream ended before message_start
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/427e894fd2bed403.
Report an issue: GitHub.