zeroclaw-labs/zeroclaw · error · anyhow::Error
Slack outbound attachment path must be absolute: {target}
Error message
Slack outbound attachment path must be absolute: {target} What it means
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.
Source
Thrown at crates/zeroclaw-channels/src/slack.rs:1036
"channel.slack",
self.proxy_url.as_deref(),
30,
10,
)
}
async fn resolve_outbound_attachment_marker(
&self,
marker: &SlackOutboundAttachmentMarker,
) -> anyhow::Result<MediaAttachment> {
let target = marker.target.trim();
if target.starts_with("file:") || target.starts_with("data:") || target.contains("://") {
anyhow::bail!("Slack outbound attachment target must be a local workspace path");
}
let path = Path::new(target);
if !path.is_absolute() {
anyhow::bail!("Slack outbound attachment path must be absolute: {target}");
}
let workspace = self
.workspace_dir
.as_deref()
.context("Slack outbound local attachments require workspace_dir")?;
let canonical_workspace = tokio::fs::canonicalize(workspace).await.with_context(|| {
format!(
"failed to canonicalize Slack workspace {}",
workspace.display()
)
})?;
let canonical_path = tokio::fs::canonicalize(path)
.await
.with_context(|| format!("Slack outbound attachment path not found: {target}"))?;
if !canonical_path.starts_with(&canonical_workspace) {
anyhow::bail!(View on GitHub (pinned to 88bb9c8533)
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
Example fix
// before
let msg = format!("[image:{}]", rel_path.display()); // e.g. attachments/chart.png
// after
let abs = workspace_dir.join(&rel_path);
let msg = format!("[image:{}]", abs.display()); Defensive patterns
Strategy: validation
Validate before calling
fn build_attachment_marker(kind: &str, workspace_dir: &Path, rel: impl AsRef<Path>) -> String {
let abs = workspace_dir.join(rel); // markers must carry absolute paths
format!("[{kind}:{}]", abs.display())
} Type guard
fn marker_target_is_absolute(target: &str) -> bool {
std::path::Path::new(target).is_absolute()
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Slack outbound attachment target must be a local workspace p
- Slack outbound attachment target is not a file: {}
- Slack outbound attachment path escapes workspace: {}
- attachment path not found: {}
- inbound attachment exceeds {} MB limit
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/0dbb710fcfb02eef.
Report an issue: GitHub.