You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When using reasoning models that generate extensive thinking traces (such as GLM-5.3-flash with max thinking or DeepSeek models), long generations that exceed 5 minutes are consistently aborted mid-stream with: Error: Command Code API request timed out after 300000ms / Operation aborted
This happens even though the server is actively and continuously streaming tokens without any pauses or network drops.
Evidence & Observation
Looking at the session timestamps, the abort occurs at exactly the 300-second mark:
Turn start to abort: 300.010s (while actively streaming thinking tokens)
Next retry to abort: 300.009s
The 300,000 ms duration originates from Pi's default httpIdleTimeoutMs setting (300_000), which is forwarded to the provider as options.timeoutMs.
Analysis
In src/core.ts (around lines 617-622), attemptTimeoutId is started when the request attempt begins:
In the stream reading loop (readLoop: for (;;) around lines 707-730):
const{ done, value }=awaitraceAbort(reader.read(),attemptController.signal)
The timer is not refreshed when reader.read() yields new chunks. It is only cleared in the finally block once the entire response has completed. As a result, timeoutMs currently acts as a hard wall-clock cap on the total duration of the attempt, rather than an idle gap timeout between received chunks.
Comparison with Pi Core Semantics
In Pi core (@earendil-works/pi-coding-agent), httpIdleTimeoutMs is documented and implemented as an idle timeout:
For HTTP/SSE via Undici (http-dispatcher.ts), bodyTimeout monitors the time between bytes received over the socket, resetting on every chunk.
For WebSocket streams (e.g., in openai-codex-responses.ts), the idle timer is rescheduled on each message.
The safeguard introduced in PR #13 to ensure the stream does not hang indefinitely mid-consumption is completely valid and necessary. However, resetting the timer whenever active data is received would preserve that protection while allowing legitimate, long-running reasoning streams to finish.
Suggested Improvement
One possible approach is to refresh attemptTimeoutId inside readLoop upon receiving each chunk:
const{ done, value }=awaitraceAbort(reader.read(),attemptController.signal)if(timeoutMs!==undefined){clearAttemptTimeout()attemptTimeoutId=setTimeout(()=>{attemptTimedOut=trueattemptController.abort()},timeoutMs)}
Problem
When using reasoning models that generate extensive thinking traces (such as GLM-5.3-flash with max thinking or DeepSeek models), long generations that exceed 5 minutes are consistently aborted mid-stream with:
Error: Command Code API request timed out after 300000ms/Operation abortedThis happens even though the server is actively and continuously streaming tokens without any pauses or network drops.
Evidence & Observation
Looking at the session timestamps, the abort occurs at exactly the 300-second mark:
300.010s(while actively streaming thinking tokens)300.009sThe 300,000 ms duration originates from Pi's default
httpIdleTimeoutMssetting (300_000), which is forwarded to the provider asoptions.timeoutMs.Analysis
In
src/core.ts(around lines 617-622),attemptTimeoutIdis started when the request attempt begins:In the stream reading loop (
readLoop: for (;;)around lines 707-730):The timer is not refreshed when
reader.read()yields new chunks. It is only cleared in thefinallyblock once the entire response has completed. As a result,timeoutMscurrently acts as a hard wall-clock cap on the total duration of the attempt, rather than an idle gap timeout between received chunks.Comparison with Pi Core Semantics
In Pi core (
@earendil-works/pi-coding-agent),httpIdleTimeoutMsis documented and implemented as an idle timeout:http-dispatcher.ts),bodyTimeoutmonitors the time between bytes received over the socket, resetting on every chunk.openai-codex-responses.ts), the idle timer is rescheduled on each message.The safeguard introduced in PR #13 to ensure the stream does not hang indefinitely mid-consumption is completely valid and necessary. However, resetting the timer whenever active data is received would preserve that protection while allowing legitimate, long-running reasoning streams to finish.
Suggested Improvement
One possible approach is to refresh
attemptTimeoutIdinsidereadLoopupon receiving each chunk:With this adjustment:
timeoutMs, the attempt still aborts as intended by PR feat(core): add retry for transient HTTP and stream-level errors #13.Would you be open to a PR implementing this idle timeout reset, along with a test case for continuous long streams?