{"record":{"id":"cebb60baca23c718","repo":"zeroclaw-labs/zeroclaw","slug":"matrix-marker-url-url-returned-http-status-sta","errorCode":null,"errorMessage":"matrix: marker URL {url} returned HTTP status {status}","messagePattern":"matrix: marker URL (.+?) returned HTTP status (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/matrix.rs","lineNumber":3306,"sourceCode":"            reqwest::Client::builder()\n                .timeout(MARKER_HTTP_TIMEOUT)\n                .redirect(redirect_policy)\n                .user_agent(\"zeroclaw-matrix/1.0\")\n                .build()\n                .expect(\"default reqwest client config never fails to build\")\n        })\n    }\n\n    pub(super) async fn fetch_http(url: reqwest::Url) -> Result<Vec<u8>> {\n        let client = marker_http_client();\n        let resp = client\n            .get(url.clone())\n            .send()\n            .await\n            .with_context(|| format!(\"fetch marker URL {url}\"))?;\n        let status = resp.status();\n        if !status.is_success() {\n            bail!(\"matrix: marker URL {url} returned HTTP status {status}\");\n        }\n        let mut stream = resp.bytes_stream();\n        let mut buf = Vec::new();\n        while let Some(chunk) = stream.next().await {\n            let chunk = chunk.with_context(|| format!(\"stream chunk from {url}\"))?;\n            if buf.len().saturating_add(chunk.len()) > MAX_MARKER_BYTES {\n                bail!(\"matrix: marker URL {url} exceeded {MAX_MARKER_BYTES}-byte cap; refusing\");\n            }\n            buf.extend_from_slice(&chunk);\n        }\n        Ok(buf)\n    }\n\n    pub(super) fn thread_anchor_from_message(\n        outbox: &Outbox<'_>,\n        message: &SendMessage,\n    ) -> Option<OwnedEventId> {\n        if outbox.reply_in_thread {","sourceCodeStart":3288,"sourceCodeEnd":3324,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/matrix.rs#L3288-L3324","documentation":"Marker delivery (content referenced by URL in outgoing messages) fetches the marker over HTTP via fetch_http, with an SSRF-guarded redirect policy (redirects to private hosts are blocked) and a 30-second timeout. A non-success HTTP status from the marker URL aborts the fetch with this message; the status code distinguishes a dead link (404), a permission problem (403/410), or a serving-side failure (429/5xx). This fetch is separate from Matrix API calls.","triggerScenarios":"Delivering a message whose marker URL answers non-2xx: expired signed sharing link (403/410), deleted object (404), rate-limited or broken host (429/5xx), or a mistyped scheme/host in the marker URL.","commonSituations":"Signed S3/GCS URLs that expired before send; content moved or deleted at the origin; internal hosts unreachable from the agent's network; CDN hiccups; markdown pasted with a truncated URL.","solutions":["Open the exact URL from the error (printed verbatim) with curl from the same host and inspect the status.","If signed URLs expired: regenerate them, or pre-download the content and reference it from the workspace dir instead of a remote URL.","If 5xx/429: retry the send after a backoff - the marker host is temporarily failing.","Verify the marker host is reachable from the agent's network (egress rules, DNS, proxy) and is public, since redirects to private hosts are refused."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"async fn marker_url_reachable(url: &str) -> bool {\n    matches!(\n        reqwest::Client::builder()\n            .timeout(std::time::Duration::from_secs(10))\n            .build()\n            .expect(\"default client config\")\n            .head(url)\n            .send()\n            .await,\n        Ok(resp) if resp.status().is_success()\n    )\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Attach large or long-lived media from the workspace directory rather than remote URLs - no network dependency at send time.","Use long-lived or non-expiring links for markers; refresh signed URLs before they lapse.","Pre-flight marker URLs with a HEAD request when messages are queued.","Keep marker hosts public - the fetch's SSRF guard refuses redirects to private hosts."],"tags":["matrix","marker","http-status","attachment","fetch"],"backgroundTag":"http-api-error-status","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}