{"record":{"id":"0dbb710fcfb02eef","repo":"zeroclaw-labs/zeroclaw","slug":"slack-outbound-attachment-path-must-be-absolute","errorCode":null,"errorMessage":"Slack outbound attachment path must be absolute: {target}","messagePattern":"Slack outbound attachment path must be absolute: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/slack.rs","lineNumber":1036,"sourceCode":"            \"channel.slack\",\n            self.proxy_url.as_deref(),\n            30,\n            10,\n        )\n    }\n\n    async fn resolve_outbound_attachment_marker(\n        &self,\n        marker: &SlackOutboundAttachmentMarker,\n    ) -> anyhow::Result<MediaAttachment> {\n        let target = marker.target.trim();\n        if target.starts_with(\"file:\") || target.starts_with(\"data:\") || target.contains(\"://\") {\n            anyhow::bail!(\"Slack outbound attachment target must be a local workspace path\");\n        }\n\n        let path = Path::new(target);\n        if !path.is_absolute() {\n            anyhow::bail!(\"Slack outbound attachment path must be absolute: {target}\");\n        }\n\n        let workspace = self\n            .workspace_dir\n            .as_deref()\n            .context(\"Slack outbound local attachments require workspace_dir\")?;\n        let canonical_workspace = tokio::fs::canonicalize(workspace).await.with_context(|| {\n            format!(\n                \"failed to canonicalize Slack workspace {}\",\n                workspace.display()\n            )\n        })?;\n        let canonical_path = tokio::fs::canonicalize(path)\n            .await\n            .with_context(|| format!(\"Slack outbound attachment path not found: {target}\"))?;\n\n        if !canonical_path.starts_with(&canonical_workspace) {\n            anyhow::bail!(","sourceCodeStart":1018,"sourceCodeEnd":1054,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/slack.rs#L1018-L1054","documentation":"Raised by SlackChannel::resolve_outbound_attachment_marker when the marker's target passes the URL guard but Path::new(target).is_absolute() is false — e.g. [image:attachments/chart.png]. Slack outbound attachment resolution joins the path against the canonicalized workspace_dir, so it needs an absolute path to canonicalize; relative targets are rejected rather than guessed.","triggerScenarios":"An outbound attachment marker carries a relative path ('uploads/x.png', 'chart.png'); the check at slack.rs:1036 bails before canonicalization and the workspace containment test.","commonSituations":"Agents emitting paths relative to the workspace root because that reads more naturally; code building markers from PathBuf values that were joined relatively; copying example marker syntax with a shortened path.","solutions":["Emit the absolute path: join the relative path with workspace_dir when building the marker string","Ensure the file exists at the absolute location before sending (a nonexistent path produces the adjacent 'not found' context error)","Keep markers stable once sent — the same absolute path must remain valid for re-sends"],"exampleFix":"// before\nlet msg = format!(\"[image:{}]\", rel_path.display()); // e.g. attachments/chart.png\n\n// after\nlet abs = workspace_dir.join(&rel_path);\nlet msg = format!(\"[image:{}]\", abs.display());","handlingStrategy":"validation","validationCode":"fn build_attachment_marker(kind: &str, workspace_dir: &Path, rel: impl AsRef<Path>) -> String {\n    let abs = workspace_dir.join(rel); // markers must carry absolute paths\n    format!(\"[{kind}:{}]\", abs.display())\n}","typeGuard":"fn marker_target_is_absolute(target: &str) -> bool {\n    std::path::Path::new(target).is_absolute()\n}","tryCatchPattern":null,"preventionTips":["Always join relative asset paths with workspace_dir when composing marker text","Assert marker targets are absolute in tests that generate outgoing messages","Keep workspace_dir configured for the Slack channel — relative markers can never resolve without it"],"tags":["slack","attachment","validation","absolute-path","path","workspace"],"backgroundTag":"path-validation-failed","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}