{"record":{"id":"cb23b4a41a62f35b","repo":"zeroclaw-labs/zeroclaw","slug":"qq-attachment-path-not-found-target","errorCode":null,"errorMessage":"QQ attachment path not found: {target}","messagePattern":"QQ attachment path not found: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/qq.rs","lineNumber":970,"sourceCode":"\n        if target.starts_with(\"http://\") || target.starts_with(\"https://\") {\n            // URL upload — no caching (remote content may change)\n            let (file_info, _ttl) = self\n                .upload_media(\n                    recipient,\n                    attachment.kind,\n                    Some(target),\n                    None,\n                    file_name.as_deref(),\n                )\n                .await?;\n            self.send_media_message(recipient, &file_info, in_reply_to)\n                .await?;\n        } else {\n            // Local file upload\n            let path = Path::new(target);\n            if !path.exists() {\n                anyhow::bail!(\"QQ attachment path not found: {target}\");\n            }\n\n            let metadata = tokio::fs::metadata(path).await?;\n            if metadata.len() > QQ_MAX_UPLOAD_BYTES {\n                anyhow::bail!(\n                    \"QQ attachment too large ({} bytes, max {}): {target}\",\n                    metadata.len(),\n                    QQ_MAX_UPLOAD_BYTES\n                );\n            }\n\n            let file_bytes = tokio::fs::read(path).await?;\n            let (scope_label, target_id) = Self::resolve_recipient(recipient);\n            let scope = if scope_label == \"groups\" {\n                \"group\"\n            } else {\n                \"c2c\"\n            };","sourceCodeStart":952,"sourceCodeEnd":988,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/qq.rs#L952-L988","documentation":"send_attachment treats any target that is not an http(s) URL as a local file path and bails when Path::exists() is false, before any size check or upload. The target comes from attachment markers embedded in agent output, e.g. [DOCUMENT:/path/to/report.pdf] or [IMAGE:/tmp/chart.png].","triggerScenarios":"A marker references a file that was never created, was already deleted, or whose path is wrong — including relative paths resolved against the process working directory rather than the intended one.","commonSituations":"Agent steps that generate a file in one workspace and reference it with a path from another; /tmp cleanup between generation and send; relative paths in markers while the daemon runs from a different cwd; tilde paths that are not expanded.","solutions":["Use absolute paths in attachment markers","Verify the file-generation step completed (and logged success) before the send step runs","Fix typos/case in the path — the error message includes the exact target that was checked"],"exampleFix":"# before\nReport ready: [DOCUMENT:output/report.pdf]     # relative path, wrong cwd -> bail\n\n# after\nReport ready: [DOCUMENT:/var/lib/zeroclaw/work/report.pdf]","handlingStrategy":"validation","validationCode":"// Validate every local attachment target before calling send:\nasync fn attachments_exist(targets: &[String]) -> anyhow::Result<()> {\n    for t in targets {\n        if !t.starts_with(\"http://\") && !t.starts_with(\"https://\") {\n            anyhow::ensure!(Path::new(t).exists(), \"attachment path not found: {t}\");\n        }\n    }\n    Ok(())\n}","typeGuard":"fn is_local_missing_target(target: &str) -> bool {\n    !(target.starts_with(\"http://\") || target.starts_with(\"https://\"))\n        && !Path::new(target).exists()\n}\n\nassert!(is_local_missing_target(\"/no/such/file.png\"));\nassert!(!is_local_missing_target(\"https://example.com/a.png\"));","tryCatchPattern":"match ch.send(&msg).await {\n    Err(e) if e.to_string().starts_with(\"QQ attachment path not found\") => {\n        // Deterministic local-state problem: extract the path from the error, regenerate\n        // or remove the marker, then resend; never retry unchanged.\n    }\n    rest => rest?,\n}","preventionTips":["Use absolute paths in every attachment marker produced by agents/pipelines","Order steps so file generation completes (and is verified) before the send step","For /tmp-based artifacts, send immediately after generation or move them to a stable workspace directory"],"tags":["qq","attachments","file-not-found","paths"],"backgroundTag":"file-not-found","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}