{"record":{"id":"028df9a0fa5be91c","repo":"herdrdev/herdr","slug":"timed-out-reading-stream-frame-header","errorCode":null,"errorMessage":"timed out reading stream frame header","messagePattern":"timed out reading stream frame header","errorType":"error_code","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"src/api/server/pane_graphics_stream.rs","lineNumber":405,"sourceCode":"        loop {\n            if !stream_is_running(running, stream_active) {\n                return Ok(None);\n            }\n            ensure_before_deadlines(\n                idle_deadline,\n                total_deadline,\n                \"timed out reading stream frame header\",\n            )?;\n            match stream.read(&mut byte) {\n                Ok(0) => return Ok(None),\n                Ok(_) => {\n                    wait.on_progress();\n                    let now = Instant::now();\n                    let total_deadline_at =\n                        *total_deadline.get_or_insert_with(|| now + total_timeout);\n                    idle_deadline = Some(now + idle_timeout);\n                    if now >= total_deadline_at {\n                        return Err(io::Error::new(\n                            io::ErrorKind::TimedOut,\n                            \"timed out reading stream frame header\",\n                        ));\n                    }\n                    bytes.push(byte[0]);\n                    if byte[0] == b'\\n' {\n                        return String::from_utf8(bytes)\n                            .map(Some)\n                            .map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err));\n                    }\n                    if bytes.len() > max_bytes {\n                        return Err(io::Error::new(\n                            io::ErrorKind::InvalidData,\n                            \"stream frame header is too large\",\n                        ));\n                    }\n                }\n                Err(err) if read_should_retry(&err) => {","sourceCodeStart":387,"sourceCodeEnd":423,"githubUrl":"https://github.com/herdrdev/herdr/blob/f457cff4f2648eee85d176f8a41861241d4e8428/src/api/server/pane_graphics_stream.rs#L387-L423","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Increase the total timeout budget for the stream read so slow producers fit.","Investigate why the producer stalls mid-header (check the child process / upstream writer).","If the stream is genuinely dead, treat this as EOF: close the stream and re-open the graphics session instead of looping.","In tests, keep partial-header fixtures consistent with the configured deadline (first byte then silence must exceed total_timeout to reproduce deterministically)."],"exampleFix":"// before\nlet line = read_line(&mut stream, idle, Duration::from_millis(100)).await?;\n\n// after\nlet line = read_line(&mut stream, idle, Duration::from_secs(5)).await?;","handlingStrategy":"retry","validationCode":"null","typeGuard":null,"tryCatchPattern":"match read_line(&mut stream, idle, total).await {\n    Err(e) if e.kind() == io::ErrorKind::TimedOut => {\n        // header budget exhausted: close and reopen the graphics stream; do not reuse partial header bytes\n    }\n    other => other,\n}","preventionTips":["Set the total header deadline generously relative to producer speed.","Monitor producer health so stalled writers are killed instead of timing out readers.","Discard partial state and resynchronize after any stream timeout."],"tags":["streaming","graphics","timeout","header","rust"],"backgroundTag":"stream-read-timeout","analyzedSha":"f457cff4f2648eee85d176f8a41861241d4e8428","analyzedAt":"2026-08-28T15:41:09.197Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}