{"record":{"id":"0bee1f77d15cf495","repo":"block/buzz","slug":"io-line-exceeds-max-max-bytes","errorCode":null,"errorMessage":"io: line exceeds max ({max} bytes)","messagePattern":"io: line exceeds max \\((.+?) bytes\\)","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/buzz-agent/src/wire.rs","lineNumber":408,"sourceCode":") -> std::io::Result<Option<String>> {\n    let mut buf: Vec<u8> = Vec::new();\n    loop {\n        let chunk = stdin.fill_buf().await?;\n        if chunk.is_empty() {\n            if !buf.is_empty() {\n                tracing::error!(\n                    \"io: unterminated frame at EOF ({} bytes dropped)\",\n                    buf.len()\n                );\n            }\n            return Ok(None);\n        }\n        let take = chunk\n            .iter()\n            .position(|b| *b == b'\\n')\n            .map_or(chunk.len(), |i| i + 1);\n        if buf.len().saturating_add(take) > max {\n            return Err(std::io::Error::new(\n                std::io::ErrorKind::InvalidData,\n                format!(\"io: line exceeds max ({max} bytes)\"),\n            ));\n        }\n        buf.extend_from_slice(&chunk[..take]);\n        stdin.consume(take);\n        if buf.ends_with(b\"\\n\") {\n            buf.pop();\n            if buf.ends_with(b\"\\r\") {\n                buf.pop();\n            }\n            match String::from_utf8(buf) {\n                Ok(s) => return Ok(Some(s)),\n                Err(_) => {\n                    return Err(std::io::Error::new(\n                        std::io::ErrorKind::InvalidData,\n                        \"io: frame contains invalid UTF-8\",\n                    ))","sourceCodeStart":390,"sourceCodeEnd":426,"githubUrl":"https://github.com/block/buzz/blob/eed74bde2f4797714335ac10c56c0b0244c1def4/crates/buzz-agent/src/wire.rs#L390-L426","documentation":"buzz-agent speaks newline-delimited JSON over stdio. wire::read_bounded_line accumulates chunks from BufReader<Stdin> until a '\\n'; before extending, it checks the accumulated size against the caller-supplied cap and returns InvalidData 'io: line exceeds max' when a single frame would exceed it. The cap is cfg.max_line_bytes (BUZZ_AGENT_MAX_LINE_BYTES, default 4 MiB, with a MIN_LINE_BYTES floor — see config.rs:461,616,708). The error is fatal to the reader loop in lib.rs:214, so the agent process terminates the session.","triggerScenarios":"read_bounded_line(&mut stdin, max_line) errors when one stdin line exceeds max bytes before its '\\n': a session/new or turn request embedding a multi-megabyte base64 image, a huge tool result or history echoed back in one JSON frame, or a client that streams a single JSON object without a newline for more than BUZZ_AGENT_MAX_LINE_BYTES bytes.","commonSituations":"ACP harness piping screenshot attachments inline as base64 (each image inflates ~4/3x); default 4 MiB cap met by long agentic sessions with accumulated history; someone lowers BUZZ_AGENT_MAX_LINE_BYTES for testing and forgets; a misbehaving writer never sends '\\n' so the whole stream is treated as one line.","solutions":["Raise the cap: `export BUZZ_AGENT_MAX_LINE_BYTES=16777216` (16 MiB) and restart the agent — must stay above MIN_LINE_BYTES.","Shrink the payload: send images/files by reference or chunk tool results instead of inlining multi-MB base64 in one frame.","Verify the sender terminates every JSON frame with '\\n' — an unterminated stream grows the buffer until it trips the cap.","Check for accidentally duplicated payloads (history echoed twice) inflating frame size."],"exampleFix":"# before: default 4 MiB cap, agent dies on a 6 MB base64 frame\n# io: line exceeds max (4194304 bytes)\n\n# after\nexport BUZZ_AGENT_MAX_LINE_BYTES=16777216","handlingStrategy":"validation","validationCode":"// sender-side guard before writing a frame to the agent's stdin\nfn frame_fits(line: &str, max: usize) -> bool {\n    line.len() + 1 <= max // +1 for the trailing newline\n}\nassert!(frame_fits(&json_line, cfg.max_line_bytes));","typeGuard":"fn is_within_line_budget(line: &str, max_line_bytes: usize) -> bool {\n    line.len() < max_line_bytes\n}","tryCatchPattern":"// on the reader side, treat an over-limit line as a protocol error for THIS frame\n// rather than killing the loop if you control the harness:\nmatch wire::read_bounded_line(&mut stdin, max_line).await {\n    Ok(Some(line)) => handle(line),\n    Ok(None) => break,\n    Err(e) if e.kind() == std::io::ErrorKind::InvalidData => { /* log frame size, skip session */ break }\n    Err(e) => return Err(e),\n}","preventionTips":["Size BUZZ_AGENT_MAX_LINE_BYTES to the largest base64 payload you intend to inline (remember ~1.37x inflation).","Pass large blobs by reference/attachment id instead of embedding them in stdio frames.","Always terminate frames with '\\n' in harnesses; unterminated streams eventually trip this cap.","Log frame sizes on the producer side in dev to see how close you run to the 4 MiB default."],"tags":["rust","stdio","ndjson","payload-size","acl-protocol","configuration"],"backgroundTag":"payload-too-large","analyzedSha":"eed74bde2f4797714335ac10c56c0b0244c1def4","analyzedAt":"2026-08-20T04:38:24.874Z","contentChangedAt":"2026-08-20T04:38:24.874Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}