{"record":{"id":"8df68ce1b6cbec41","repo":"zeroclaw-labs/zeroclaw","slug":"response-body-content-length-content-length-exce","errorCode":null,"errorMessage":"response body content length {content_length} exceeds {max_bytes}-byte limit","messagePattern":"response body content length (.+?) exceeds (.+?)-byte limit","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/util.rs","lineNumber":52,"sourceCode":"    if max_bytes >= s.len() {\n        return s.len();\n    }\n    let mut end = max_bytes;\n    while end > 0 && !s.is_char_boundary(end) {\n        end -= 1;\n    }\n    end\n}\n\n#[cfg(any(feature = \"channel-mattermost\", feature = \"channel-qq\"))]\npub(crate) async fn read_response_body_limited(\n    mut response: reqwest::Response,\n    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)","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/util.rs#L34-L70","documentation":"read_response_body_limited (shared by the Mattermost and QQ channels) enforces a byte cap on API response bodies. Before reading anything, it compares the Content-Length header against max_bytes and bails immediately when the declared size is over the limit, so an oversized body is rejected without transferring it.","triggerScenarios":"A Mattermost/QQ API response whose Content-Length header exceeds the caller-supplied cap: fetching a very large file/attachment payload, an oversized channel export, or a server mis-declaring length. The unit tests exercise exactly this declared-oversize path.","commonSituations":"Downloading attachments or history blobs through a channel client that was sized for ordinary message JSON. A peer server returning an unexpectedly huge serialized payload trips the guard that exists to bound memory use.","solutions":["Avoid routing oversized resources through the bounded reader: fetch large attachments via a dedicated download path with its own higher cap.","If the payload is legitimately large and trusted, call a reader with a higher max_bytes instead of bypassing bounds entirely.","Confirm the server is not mis-declaring Content-Length (compare with actual body size)."],"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(\"content length\") => {\n        // Declared oversize: not retryable with the same cap. Route the\n        // resource to a dedicated download path with its own limit.\n        Err(err.context(\"payload over channel body cap — use a bounded download path\"))\n    }\n    Err(err) => Err(err),\n}","preventionTips":["Do not fetch large attachments/exports through the shared channel client; use a dedicated reader with a purpose-sized cap.","Size max_bytes above the largest legitimate payload (plus encoding overhead) measured in practice.","Treat this guard as load-bearing: it bounds memory, so never bypass it by reading the stream manually."],"tags":["http","response-body","limit","mattermost","qq"],"backgroundTag":"response-body-too-large","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}