zeroclaw-labs/zeroclaw · error · anyhow::Error

matrix: marker URL {url} exceeded {MAX_MARKER_BYTES}-byte ca

Error message

matrix: marker URL {url} exceeded {MAX_MARKER_BYTES}-byte cap; refusing

What it means

fetch_http streams the marker body and enforces a hard MAX_MARKER_BYTES cap of 8 MiB (8 * 1024 * 1024): once the buffered size plus the incoming chunk would cross the cap it refuses and aborts rather than buffer an unbounded remote resource. The fetch also runs under a 30-second timeout. Hitting it means the referenced marker content is larger than the channel will accept.

Source

Thrown at crates/zeroclaw-channels/src/matrix.rs:3313

    }

    pub(super) async fn fetch_http(url: reqwest::Url) -> Result<Vec<u8>> {
        let client = marker_http_client();
        let resp = client
            .get(url.clone())
            .send()
            .await
            .with_context(|| format!("fetch marker URL {url}"))?;
        let status = resp.status();
        if !status.is_success() {
            bail!("matrix: marker URL {url} returned HTTP status {status}");
        }
        let mut stream = resp.bytes_stream();
        let mut buf = Vec::new();
        while let Some(chunk) = stream.next().await {
            let chunk = chunk.with_context(|| format!("stream chunk from {url}"))?;
            if buf.len().saturating_add(chunk.len()) > MAX_MARKER_BYTES {
                bail!("matrix: marker URL {url} exceeded {MAX_MARKER_BYTES}-byte cap; refusing");
            }
            buf.extend_from_slice(&chunk);
        }
        Ok(buf)
    }

    pub(super) fn thread_anchor_from_message(
        outbox: &Outbox<'_>,
        message: &SendMessage,
    ) -> Option<OwnedEventId> {
        if outbox.reply_in_thread {
            message
                .thread_ts
                .as_deref()
                .filter(|s| !s.is_empty())
                .and_then(|s| s.parse().ok())
        } else {
            None

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Shrink the marker resource below 8 MiB (compress or resize images; pick a thumbnail variant).
  2. If the content must stay large, attach it from the workspace directory (uploaded through Matrix media) instead of a marker URL.
  3. Verify the URL returns the intended asset (check Content-Type and Content-Length) and not an HTML page or archive.
  4. If a larger cap is genuinely required for your deployment, raise MAX_MARKER_BYTES in a fork, accepting the memory-cost tradeoff.
Defensive patterns

Strategy: validation

Validate before calling

const MAX_MARKER_BYTES: u64 = 8 * 1024 * 1024;

async fn marker_size_ok(url: &str) -> bool {
    match reqwest::Client::new().head(url).send().await {
        Ok(resp) => resp
            .headers()
            .get(reqwest::header::CONTENT_LENGTH)
            .and_then(|v| v.to_str().ok())
            .and_then(|v| v.parse::<u64>().ok())
            .map_or(true, |len| len <= MAX_MARKER_BYTES),
        Err(_) => false,
    }
}

Prevention

When it happens

Trigger: Delivering a message whose marker URL serves a body larger than 8 MiB - large images, videos, or misconfigured endpoints that stream endless data - so the running byte total crosses MAX_MARKER_BYTES mid-stream.

Common situations: Pointing markers at full-resolution media or video files; a URL that returns a directory listing or generated archive; a CDN serving the raw asset instead of a thumbnail; content-type mistakes where a page downloads instead of the intended small asset.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/06b863eb072d0b04. Report an issue: GitHub.