Repository navigation
Infinite retry loop when StreamIdleTimeoutError occurs during tool input generation #12233
Copy link
Copy link
Closed
Closed
Copy link
Labels
perfIndicates a performance issue or need for optimizationIndicates a performance issue or need for optimization
Description
Summary
When a model attempts to generate a large tool input (e.g., writing a full page of content), the stream can stall and trigger a StreamIdleTimeoutError. This error is marked as retryable, causing an infinite loop where the model repeatedly attempts the same failing operation.
Reproduction
- Ask the agent to write a large file (e.g., "create a full product page with multiple sections")
- The model starts generating a
writetool call with largecontentparameter - The API stalls during tool input generation (possibly due to rate limiting or output token limits)
- After 60 seconds,
StreamIdleTimeoutErroris thrown - Error is retried with exponential backoff
- Model sees previous attempt failed with "Tool execution aborted"
- Model tries the exact same approach
- Loop continues indefinitely
Evidence from Logs
ERROR 2026-02-05T02:51:10 service=session.processor error=Stream idle timeout: no data received for 60000ms
ERROR 2026-02-05T02:52:16 service=session.processor error=Stream idle timeout: no data received for 60000ms
ERROR 2026-02-05T02:53:23 service=session.processor error=Stream idle timeout: no data received for 60000ms
ERROR 2026-02-05T02:54:34 service=session.processor error=Stream idle timeout: no data received for 60000ms
Task verification showed 10 consecutive write attempts with empty inputs:
## Tools Used
write: {}
write: {}
write: {}
write: {}
write: {}
write: {}
write: {}
write: {}
write: {}
write: {}
Root Cause Analysis
The Retry Loop
Model generates Write tool with large content
↓
API stalls during tool input JSON generation
↓
60 seconds pass with no stream chunks
↓
StreamIdleTimeoutError thrown (processor.ts:44)
↓
Converted to APIError with isRetryable: true (message-v2.ts:715)
↓
retry.ts.retryable() returns message string
↓
processor.ts catches, increments attempt, waits, continues (line 403-420)
↓
New LLM.stream() starts fresh
↓
Model sees "Tool execution aborted" error, tries same approach
↓
INFINITE LOOP
Why Doom Loop Detection Doesn't Trigger
The existing doom loop detection (processor.ts:207-232) checks:
if (part.state.status === "running" && part.state.input) {
// Track same tool + same input called 3 times
}But this fails because:
- Stream dies during
tool-input-startphase (beforetool-call) - Tool never reaches "running" status
- Input is always
{}(empty) - JSON never completed - Cleanup marks tool as "error" with empty input
- Each retry has a different tool call ID, so not detected as duplicate
Suggested Fixes
Option 1: Add max retries for StreamIdleTimeoutError
// In processor.ts
let idleTimeoutRetries = 0
const MAX_IDLE_TIMEOUT_RETRIES = 3
// In catch block:
if (e instanceof StreamIdleTimeoutError) {
idleTimeoutRetries++
if (idleTimeoutRetries >= MAX_IDLE_TIMEOUT_RETRIES) {
input.assistantMessage.error = MessageV2.fromError(
new Error("Stream repeatedly timed out. The model may be trying to generate too much content at once."),
{ providerID: input.model.providerID }
)
return "stop"
}
}Option 2: Detect repeated incomplete tool calls
// Track tools that fail during input generation
const incompleteToolAttempts: Record<string, number> = {}
// When tool-input-start fires but stream dies before tool-call:
if (part.type === "tool" && Object.keys(part.state.input || {}).length === 0) {
incompleteToolAttempts[part.tool] = (incompleteToolAttempts[part.tool] || 0) + 1
if (incompleteToolAttempts[part.tool] >= DOOM_LOOP_THRESHOLD) {
// Trigger doom loop - stop and surface error
}
}Option 3: Better error message to help model recover
Instead of generic "Tool execution aborted", provide actionable guidance:
"Tool execution aborted: stream timed out after 60s while generating tool input.
This often happens when writing very large content. Try breaking the write into smaller chunks."
Environment
- Provider: github-copilot
- Model: claude-opus-4.5
- Stream idle timeout: 60000ms (default)
- Tool: write
Related Code
packages/opencode/src/session/processor.ts- Stream processing, idle timeout, doom loop detectionpackages/opencode/src/session/message-v2.ts- StreamIdleTimeoutError class, error conversionpackages/opencode/src/session/retry.ts- Retry logicpackages/opencode/src/session/prompt.ts- Main agentic loop
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
perfIndicates a performance issue or need for optimizationIndicates a performance issue or need for optimization
Activity
dzianisv commentedon Feb 5, 2026
Closing to reopen with more detailed logs and evidence.
github-actions commentedon Feb 5, 2026
This issue might be a duplicate of existing issues. Please check:
Feel free to ignore if none of these address your specific case.
cjmayoral commentedon Feb 11, 2026
I’m seeing the same pattern on Windows (OpenCode 1.1.49) with session
ses_3b7971163ffexUgX5mjSgXDfWQ:edit) is recorded with empty input{}anderror: "Tool execution aborted".finish: "length"(output limit), suggesting the tool call was truncated before tool arguments were fully emitted.{}.Sanitized snippets available:
finish: "length"Hypothesis: when
finish: "length"happens mid tool-input, the runtime still attempts tool execution with{}, which then fails. A guard that detects incomplete tool payloads (or retries to complete tool args) would avoid this class of “Tool execution aborted”.If helpful, I can share the exact snippet JSONs.
CuteCake commentedon Feb 18, 2026
Thank you for bringing this up. I am having the same issue with WSL2 and local model.