herdrdev/herdr · error · io::Error

timed out reading stream frame header

Error message

timed out reading stream frame header

What it means

The pane graphics streaming code reads each frame's header line byte-by-byte with both an idle timeout and an absolute total deadline. If the total deadline passes while the header is still incomplete (or before it even starts), read_line returns ErrorKind::TimedOut with 'timed out reading stream frame header'. The idle deadline alone (no bytes at all) does not trigger it — only exceeding the overall budget does.

Source

Thrown at src/api/server/pane_graphics_stream.rs:405

        loop {
            if !stream_is_running(running, stream_active) {
                return Ok(None);
            }
            ensure_before_deadlines(
                idle_deadline,
                total_deadline,
                "timed out reading stream frame header",
            )?;
            match stream.read(&mut byte) {
                Ok(0) => return Ok(None),
                Ok(_) => {
                    wait.on_progress();
                    let now = Instant::now();
                    let total_deadline_at =
                        *total_deadline.get_or_insert_with(|| now + total_timeout);
                    idle_deadline = Some(now + idle_timeout);
                    if now >= total_deadline_at {
                        return Err(io::Error::new(
                            io::ErrorKind::TimedOut,
                            "timed out reading stream frame header",
                        ));
                    }
                    bytes.push(byte[0]);
                    if byte[0] == b'\n' {
                        return String::from_utf8(bytes)
                            .map(Some)
                            .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err));
                    }
                    if bytes.len() > max_bytes {
                        return Err(io::Error::new(
                            io::ErrorKind::InvalidData,
                            "stream frame header is too large",
                        ));
                    }
                }
                Err(err) if read_should_retry(&err) => {

View on GitHub (pinned to f457cff4f2)

Solutions

  1. Increase the total timeout budget for the stream read so slow producers fit.
  2. Investigate why the producer stalls mid-header (check the child process / upstream writer).
  3. If the stream is genuinely dead, treat this as EOF: close the stream and re-open the graphics session instead of looping.
  4. In tests, keep partial-header fixtures consistent with the configured deadline (first byte then silence must exceed total_timeout to reproduce deterministically).

Example fix

// before
let line = read_line(&mut stream, idle, Duration::from_millis(100)).await?;

// after
let line = read_line(&mut stream, idle, Duration::from_secs(5)).await?;
Defensive patterns

Strategy: retry

Validate before calling

null

Try / catch

match read_line(&mut stream, idle, total).await {
    Err(e) if e.kind() == io::ErrorKind::TimedOut => {
        // header budget exhausted: close and reopen the graphics stream; do not reuse partial header bytes
    }
    other => other,
}

Prevention

When it happens

Trigger: serve_frames / read_response_line reading a graphics stream whose header line trickles in slowly enough that total elapsed time exceeds total_timeout; a producer that stalls partway through writing a header (e.g. `{"type":"...` then blocks); deadlines set too tight for slow producers in tests like partial_graphics_header_times_out_after_first_byte.

Common situations: A slow or hung child process emitting graphics/OSC sequences; heavy system load stretching a normally-fast producer past the deadline; misconfigured total_timeout in stream configuration or tests; pipe backpressure from a slow consumer upstream of the producer.

Understand the failure class

Related errors


AI-assisted analysis of herdrdev/herdr@f457cff4f2 (2026-08-28). Data as JSON: /api/errors/028df9a0fa5be91c. Report an issue: GitHub.