{"record":{"id":"6c1c011edae15d1f","repo":"zeroclaw-labs/zeroclaw","slug":"response-body-exceeds-max-bytes-byte-limit","errorCode":null,"errorMessage":"response body exceeds {max_bytes}-byte limit","messagePattern":"response body exceeds (.+?)-byte limit","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/util.rs","lineNumber":65,"sourceCode":"    max_bytes: u64,\n) -> anyhow::Result<Vec<u8>> {\n    if let Some(content_length) = response.content_length()\n        && content_length > max_bytes\n    {\n        anyhow::bail!(\n            \"response body content length {content_length} exceeds {max_bytes}-byte limit\"\n        );\n    }\n\n    let mut body = Vec::new();\n\n    while let Some(chunk) = response.chunk().await? {\n        let chunk_len = u64::try_from(chunk.len()).unwrap_or(u64::MAX);\n        let next_len = u64::try_from(body.len())\n            .unwrap_or(u64::MAX)\n            .saturating_add(chunk_len);\n        if next_len > max_bytes {\n            anyhow::bail!(\"response body exceeds {max_bytes}-byte limit\");\n        }\n        body.extend_from_slice(&chunk);\n    }\n\n    Ok(body)\n}\n\n#[cfg(all(test, any(feature = \"channel-mattermost\", feature = \"channel-qq\")))]\npub(crate) async fn spawn_raw_http_response(\n    raw_response: Vec<u8>,\n    hold_open: bool,\n) -> (String, tokio::task::JoinHandle<()>) {\n    use tokio::io::{AsyncReadExt, AsyncWriteExt};\n\n    let listener = tokio::net::TcpListener::bind(\"127.0.0.1:0\").await.unwrap();\n    let address = listener.local_addr().unwrap();\n    let server = zeroclaw_spawn::spawn!(async move {\n        let (mut socket, _) = listener.accept().await.unwrap();","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/util.rs#L47-L83","documentation":"The streaming half of read_response_body_limited: when there is no usable Content-Length (Transfer-Encoding: chunked), the function accumulates chunks and checks the running total against max_bytes, bailing as soon as one more chunk would cross the cap. This bounds memory even when the server streams without declaring a size.","triggerScenarios":"A chunked response whose cumulative body exceeds max_bytes mid-stream — the function aborts partway through rather than buffering the whole thing. The unit test drives exactly this: chunks arriving until the limit is exceeded triggers the bail before the response ends.","commonSituations":"Proxy servers (nginx, load balancers) that re-encode responses as chunked, removing Content-Length, so only this streaming check protects the process. Servers streaming unbounded or corrupt payloads hit it first.","solutions":["Treat it as a hard protocol bound: do not retry the same request expecting a different size.","Move oversized transfers to a dedicated path with an explicit, purpose-sized cap.","Check whether an intermediary proxy re-chunked a response that legitimately fits, and size the cap above real payloads plus encoding overhead."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match read_response_body_limited(response, max_bytes).await {\n    Ok(body) => Ok(body),\n    Err(err) if err.to_string().contains(\"response body exceeds\") => {\n        // Chunked/streaming body crossed the cap mid-stream: abort, do not\n        // resume or retry with the same cap.\n        Err(err.context(\"streamed body over cap — move oversized transfers elsewhere\"))\n    }\n    Err(err) => Err(err),\n}","preventionTips":["Remember proxies strip Content-Length and re-encode as chunked, so only this streaming check protects you there — never assume the declared-length check is enough.","Cap request scope: paginate or range-request large resources instead of pulling them in one response.","Alert on this error: a legit service occasionally crossing the cap means the cap (or the request pattern) is wrong."],"tags":["http","response-body","chunked","limit","streaming"],"backgroundTag":"response-body-too-large","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}