{"record":{"id":"51363598b4a0983e","repo":"can1357/oh-my-pi","slug":"awaitingfirstitem-options-errormessage-optio","errorCode":null,"errorMessage":"!awaitingFirstItem ? options.errorMessage : (options.firstItemErrorMessage ?? options.errorMessage)","messagePattern":"!awaitingFirstItem \\? options\\.errorMessage : \\(options\\.firstItemErrorMessage \\?\\? options\\.errorMessage\\)","errorType":"exception","errorClass":"AIError.StreamTimeoutError","httpStatus":null,"severity":"warning","filePath":"packages/ai/src/utils/idle-iterator.ts","lineNumber":405,"sourceCode":"\t\t\t\tif (outcome.kind === \"abort\") {\n\t\t\t\t\tcloseIterator();\n\t\t\t\t\tthrow abortReason(abortSignal!);\n\t\t\t\t}\n\t\t\t\tif (outcome.kind === \"timeout\") {\n\t\t\t\t\tif (hasPendingLocalWork()) {\n\t\t\t\t\t\t// A local tool is still running; the provider cannot make\n\t\t\t\t\t\t// progress until we hand its result back. Keep waiting.\n\t\t\t\t\t\textendDeadlineForLocalWork();\n\t\t\t\t\t\tcontinuing = true;\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (!awaitingFirstItem) {\n\t\t\t\t\t\toptions.onIdle?.();\n\t\t\t\t\t} else {\n\t\t\t\t\t\toptions.onFirstItemTimeout?.();\n\t\t\t\t\t}\n\t\t\t\t\tcloseIterator();\n\t\t\t\t\tthrow new AIError.StreamTimeoutError(\n\t\t\t\t\t\t!awaitingFirstItem ? options.errorMessage : (options.firstItemErrorMessage ?? options.errorMessage),\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tif (outcome.kind === \"error\") {\n\t\t\t\t\tthrow outcome.error;\n\t\t\t\t}\n\t\t\t\tif (outcome.result.done) {\n\t\t\t\t\tmarkFirstItemReceived();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst item = outcome.result.value;\n\t\t\t\t// Non-progress items (e.g. provider keepalives, synthetic `start` events that\n\t\t\t\t// arrive before the model has produced any tokens) MUST NOT flip us out of\n\t\t\t\t// `awaitingFirstItem`. Otherwise the next iteration switches from the (longer)\n\t\t\t\t// first-item watchdog to the (shorter) idle watchdog while we're still waiting\n\t\t\t\t// on the model's first real output.\n\t\t\t\tif (isProgressItem(item)) {\n\t\t\t\t\tmarkFirstItemReceived();","sourceCodeStart":387,"sourceCodeEnd":423,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/ai/src/utils/idle-iterator.ts#L387-L423","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Tune both firstItemDeadlineMs and idleTimeoutMs so legitimate processing time between pulls doesn't trip the deadline.","Provide distinct firstItemErrorMessage and errorMessage for diagnosability.","Process chunks promptly or move heavy work off the pull path so deadlines reflect provider latency, not consumer slowness.","Catch StreamTimeoutError and retry/fallback via onFirstItemTimeout/onIdle hooks."],"exampleFix":"// before: heavy work between pulls trips the deadline\nfor await (const ev of timed) { await expensiveRender(ev); }\n// after: decouple heavy work from the pull loop\nfor await (const ev of timed) { queue.push(ev); } // render off-loop","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n\tfor await (const ev of timedStream) handle(ev);\n} catch (err) {\n\tif (err instanceof AIError.StreamTimeoutError) {\n\t\t// same family as sync paths; inspect which deadline tripped via message\n\t} else throw err;\n}","preventionTips":["Keep per-chunk consumer work cheap; offload heavy processing off the pull loop.","Provide both firstItemErrorMessage and errorMessage for triage.","Treat this like 626/627: tune deadlines, add fallback hooks."],"tags":["timeout","streaming","backpressure"],"backgroundTag":"stream-idle-timeout","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}