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

!awaitingFirstItem ? options.errorMessage : (options.firstIt

Error message

!awaitingFirstItem ? options.errorMessage : (options.firstItemErrorMessage ?? options.errorMessage)

What it means

AIError.StreamTimeoutError thrown from the deferred/racing path of iterateWithIdleTimeout (e.g. when the iterator was paused awaiting consumer work). It picks the message dynamically: options.errorMessage for a mid-stream idle timeout, or firstItemErrorMessage ?? errorMessage if the first item never arrived — mirroring the two synchronous timeout paths. This is the same contract as 626/627 surfaced through the async race branch.

Source

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

				if (outcome.kind === "abort") {
					closeIterator();
					throw abortReason(abortSignal!);
				}
				if (outcome.kind === "timeout") {
					if (hasPendingLocalWork()) {
						// A local tool is still running; the provider cannot make
						// progress until we hand its result back. Keep waiting.
						extendDeadlineForLocalWork();
						continuing = true;
						continue;
					}
					if (!awaitingFirstItem) {
						options.onIdle?.();
					} else {
						options.onFirstItemTimeout?.();
					}
					closeIterator();
					throw new AIError.StreamTimeoutError(
						!awaitingFirstItem ? options.errorMessage : (options.firstItemErrorMessage ?? options.errorMessage),
					);
				}
				if (outcome.kind === "error") {
					throw outcome.error;
				}
				if (outcome.result.done) {
					markFirstItemReceived();
					return;
				}
				const item = outcome.result.value;
				// Non-progress items (e.g. provider keepalives, synthetic `start` events that
				// arrive before the model has produced any tokens) MUST NOT flip us out of
				// `awaitingFirstItem`. Otherwise the next iteration switches from the (longer)
				// first-item watchdog to the (shorter) idle watchdog while we're still waiting
				// on the model's first real output.
				if (isProgressItem(item)) {
					markFirstItemReceived();

View on GitHub (pinned to 9690622007)

Solutions

  1. Tune both firstItemDeadlineMs and idleTimeoutMs so legitimate processing time between pulls doesn't trip the deadline.
  2. Provide distinct firstItemErrorMessage and errorMessage for diagnosability.
  3. Process chunks promptly or move heavy work off the pull path so deadlines reflect provider latency, not consumer slowness.
  4. Catch StreamTimeoutError and retry/fallback via onFirstItemTimeout/onIdle hooks.

Example fix

// before: heavy work between pulls trips the deadline
for await (const ev of timed) { await expensiveRender(ev); }
// after: decouple heavy work from the pull loop
for await (const ev of timed) { queue.push(ev); } // render off-loop
Defensive patterns

Strategy: retry

Try / catch

try {
	for await (const ev of timedStream) handle(ev);
} catch (err) {
	if (err instanceof AIError.StreamTimeoutError) {
		// same family as sync paths; inspect which deadline tripped via message
	} else throw err;
}

Prevention

When it happens

Trigger: Same conditions as the first-item (626) and idle (627) timeouts, but detected by the internal race/timer path — typically when the consumer awaits slowly between pulls or the timer fires while the generator is suspended.

Common situations: Slow downstream consumer (heavy per-chunk processing) starving the iterator past the deadline; provider stall during a paused/suspended pull; identical root causes to 626/627.

Related errors


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