zeroclaw-labs/zeroclaw · error · anyhow::Error
response body exceeds {max_bytes}-byte limit
Error message
response body exceeds {max_bytes}-byte limit What it means
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.
Source
Thrown at crates/zeroclaw-channels/src/util.rs:65
max_bytes: u64,
) -> anyhow::Result<Vec<u8>> {
if let Some(content_length) = response.content_length()
&& content_length > max_bytes
{
anyhow::bail!(
"response body content length {content_length} exceeds {max_bytes}-byte limit"
);
}
let mut body = Vec::new();
while let Some(chunk) = response.chunk().await? {
let chunk_len = u64::try_from(chunk.len()).unwrap_or(u64::MAX);
let next_len = u64::try_from(body.len())
.unwrap_or(u64::MAX)
.saturating_add(chunk_len);
if next_len > max_bytes {
anyhow::bail!("response body exceeds {max_bytes}-byte limit");
}
body.extend_from_slice(&chunk);
}
Ok(body)
}
#[cfg(all(test, any(feature = "channel-mattermost", feature = "channel-qq")))]
pub(crate) async fn spawn_raw_http_response(
raw_response: Vec<u8>,
hold_open: bool,
) -> (String, tokio::task::JoinHandle<()>) {
use tokio::io::{AsyncReadExt, AsyncWriteExt};
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
let address = listener.local_addr().unwrap();
let server = zeroclaw_spawn::spawn!(async move {
let (mut socket, _) = listener.accept().await.unwrap();View on GitHub (pinned to 88bb9c8533)
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.
Defensive patterns
Strategy: try-catch
Try / catch
match read_response_body_limited(response, max_bytes).await {
Ok(body) => Ok(body),
Err(err) if err.to_string().contains("response body exceeds") => {
// Chunked/streaming body crossed the cap mid-stream: abort, do not
// resume or retry with the same cap.
Err(err.context("streamed body over cap — move oversized transfers elsewhere"))
}
Err(err) => Err(err),
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- response body content length {content_length} exceeds {max_b
- Failed to initiate call: {}
- tenant_access_token request failed: status={status}, body={d
- bot info request failed after token refresh: status={retry_s
- bot info request failed: status={status}, body={body}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/6c1c011edae15d1f.
Report an issue: GitHub.