Skip to content

Infinite retry loop when StreamIdleTimeoutError occurs during tool input generation #12233

Closed

Description

@dzianisv

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

  1. Ask the agent to write a large file (e.g., "create a full product page with multiple sections")
  2. The model starts generating a write tool call with large content parameter
  3. The API stalls during tool input generation (possibly due to rate limiting or output token limits)
  4. After 60 seconds, StreamIdleTimeoutError is thrown
  5. Error is retried with exponential backoff
  6. Model sees previous attempt failed with "Tool execution aborted"
  7. Model tries the exact same approach
  8. 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:

  1. Stream dies during tool-input-start phase (before tool-call)
  2. Tool never reaches "running" status
  3. Input is always {} (empty) - JSON never completed
  4. Cleanup marks tool as "error" with empty input
  5. 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 detection
  • packages/opencode/src/session/message-v2.ts - StreamIdleTimeoutError class, error conversion
  • packages/opencode/src/session/retry.ts - Retry logic
  • packages/opencode/src/session/prompt.ts - Main agentic loop

Activity

  1. dzianisv commented on Feb 5, 2026

    @dzianisv
    Author

    Closing to reopen with more detailed logs and evidence.

  2. added
    perfIndicates a performance issue or need for optimization
    on Feb 5, 2026
  3. github-actions commented on Feb 5, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    Feel free to ignore if none of these address your specific case.

  4. cjmayoral commented on Feb 11, 2026

    @cjmayoral

    I’m seeing the same pattern on Windows (OpenCode 1.1.49) with session ses_3b7971163ffexUgX5mjSgXDfWQ:

    • A tool call (edit) is recorded with empty input {} and error: "Tool execution aborted".
    • The assistant message that produced it ended with finish: "length" (output limit), suggesting the tool call was truncated before tool arguments were fully emitted.
    • This matches the description of stream dying during tool input generation; the tool input never completes, leaving {}.

    Sanitized snippets available:

    • Tool error with empty input
    • Message finish: "length"
    • Session creation + model info

    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.

  5. CuteCake commented on Feb 18, 2026

    @CuteCake

    Thank you for bringing this up. I am having the same issue with WSL2 and local model.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

perfIndicates a performance issue or need for optimization

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions