Hmbown/CodeWhale · error · StreamError::DurationLimit

stream_duration_limit

stream_duration_limit

Error message

Stream exceeded maximum duration of {limit_secs}s, closing

What it means

A guard in the stream loop enforces a maximum wall-clock duration for a single model stream. When elapsed time exceeds max_duration, a StreamError::DurationLimit envelope is created, logged, sent as an error event, and the loop breaks. This bounds runaway or endless streams.

Solutions

  1. Reduce request size (shorter prompt or lower max_tokens) so the stream completes inside the limit.
  2. Raise the maximum stream duration configuration if long streams are expected for your workload.
  3. Check for model/provider degenerate behavior (repeating tokens) that inflates stream time.
  4. Split the task into smaller turns instead of one very long streamed generation.

Example fix

// before
max_stream_duration = 60s
// after
max_stream_duration = 300s  // large code-generation responses stream for minutes
Defensive patterns

Strategy: validation

Validate before calling

// estimate worst-case stream duration before sending
let est = est_tokens(prompt) * avg_secs_per_token;
if est > max_stream_duration { shrink_prompt_or_raise_limit(); }

Prevention

When it happens

Trigger: In run_turn's streaming loop, stream_start.elapsed() > max_duration on the next iteration check; the envelope message is stored in stream_error and the loop breaks.

Common situations: Models stuck emitting tokens indefinitely or in tool-call loops; a provider streaming very slowly so a huge response takes minutes; forgetting to bound extremely long agentic generations.

Understand the failure class

Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/91d11ce3a11562aa. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/core/engine/turn_loop.rs:4830

                let preview = summarize_text(pending.content.trim(), 120);
                pending_steers.push(pending);
                let _ = self
                    .tx_event
                    .send(Event::status(format!("Steer input queued: {preview}")))
                    .await;
            }

            if self.cancel_token.is_cancelled() {
                break;
            }

            // Guard: max wall-clock duration
            if stream_start.elapsed() > max_duration {
                let envelope = StreamError::DurationLimit {
                    limit_secs: max_duration_secs,
                }
                .into_envelope();
                crate::logging::warn(&envelope.message);
                stream_error.get_or_insert(envelope.message.clone());
                let _ = self.tx_event.send(Event::error(envelope)).await;
                break;
            }

            // Guard: max accumulated content bytes
            if stream_content_bytes > max_content_bytes {
                let envelope = StreamError::Overflow {
                    limit_bytes: max_content_bytes,
                }
                .into_envelope();
                crate::logging::warn(&envelope.message);
                stream_error.get_or_insert(envelope.message.clone());
                let _ = self.tx_event.send(Event::error(envelope)).await;
                break;
            }

            let event = match event_result {

View on GitHub (pinned to 73e0f67d83)