{"record":{"id":"27528b323265809c","repo":"herdrdev/herdr","slug":"stream-frame-header-is-too-large","errorCode":null,"errorMessage":"stream frame header is too large","messagePattern":"stream frame header is too large","errorType":"error_code","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"src/api/server/pane_graphics_stream.rs","lineNumber":417,"sourceCode":"                    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) => {\n                    wait.after_retry(idle_deadline, total_deadline);\n                }\n                Err(err) if is_connection_closed_error(&err) => return Ok(None),\n                Err(err) => return Err(err),\n            }\n        }\n    })\n}\n\nfn read_exact(\n    stream: &mut LocalStream,\n    len: usize,","sourceCodeStart":399,"sourceCodeEnd":435,"githubUrl":"https://github.com/herdrdev/herdr/blob/f457cff4f2648eee85d176f8a41861241d4e8428/src/api/server/pane_graphics_stream.rs#L399-L435","documentation":"While reading a graphics stream frame header, each byte is appended until newline; if the accumulated line exceeds max_bytes before the newline arrives, read_line rejects it with ErrorKind::InvalidData and 'stream frame header is too large'. This bounds header memory per frame. It almost always means the two sides disagree on framing — the reader is consuming what it thinks is a header but the writer is emitting something else (e.g. binary body bytes or a much longer line format).","triggerScenarios":"serve_frames parsing a stream where the expected header line never terminates within max_bytes: a protocol version mismatch emitting longer headers, a binary payload read as a header line, or a corrupt stream that lost the previous frame's newline.","commonSituations":"Upgrading one side of the graphics protocol but not the other (longer JSON headers); a desync after a partially-written earlier frame; a producer emitting raw image bytes where a header line was expected; tests with fixture headers exceeding the configured cap.","solutions":["Verify both reader and writer use the same stream framing protocol version (header line length cap and format).","Log the first ~max_bytes of the accumulated line to see what is actually being read — it usually reveals body bytes or a different message format.","After any earlier frame error, resynchronize by discarding until the next newline instead of continuing mid-stream.","If legitimate headers now exceed the cap, raise max_bytes for read_line consistently on both sides."],"exampleFix":"// before\nlet header = read_line(&mut stream, idle, total, 128)?;\n\n// after\nlet header = read_line(&mut stream, idle, total, 4096)?;","handlingStrategy":"validation","validationCode":"// producer side: assert header fits the reader's cap before writing\nlet header = format!(\"{json}\\n\");\nassert!(header.len() <= HEADER_MAX_BYTES, \"header {} > cap {}\", header.len(), HEADER_MAX_BYTES);","typeGuard":"fn is_valid_header_line(line: &str) -> bool {\n    !line.is_empty() && line.len() <= HEADER_MAX_BYTES && line.ends_with('\\n')\n}","tryCatchPattern":"match read_line(&mut stream, idle, total).await {\n    Err(e) if e.kind() == io::ErrorKind::InvalidData => {\n        // framing desync: dump the partial line, resync to next newline or reopen stream\n    }\n    other => other,\n}","preventionTips":["Keep both sides on the same protocol version so header formats match.","After any framing error, resynchronize instead of continuing mid-stream.","Cap header JSON size at the producer; never emit unbounded header fields."],"tags":["streaming","graphics","header-size","protocol","rust"],"backgroundTag":"message-too-large","analyzedSha":"f457cff4f2648eee85d176f8a41861241d4e8428","analyzedAt":"2026-08-28T15:41:09.197Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}