Hmbown/CodeWhale · error

fleet task '{task_id}' {field} path '{}' must be one repo-re

Error message

fleet task '{task_id}' {field} path '{}' must be one repo-relative line and cannot escape the workspace

What it means

A path in `workspace.writable_paths` failed `normalize_fleet_relative_path`: it must be a single repo-relative line. Rejected shapes are absolute paths (leading `/`, Windows drive prefixes, root-dir components), any `..` component, and embedded NUL/CR/LF characters. Backslashes are treated as separators (they are replaced with `/` first), so Windows-style paths do not slip through on Unix.

Source

Thrown at crates/tui/src/fleet/worker_runtime.rs:353

fn normalize_fleet_relative_path(
    path: &std::path::Path,
    task_id: &str,
    field: &str,
) -> Result<String> {
    let raw = path.to_string_lossy().replace('\\', "/");
    if raw.chars().any(|ch| matches!(ch, '\0' | '\r' | '\n'))
        || path.is_absolute()
        || path.components().any(|component| {
            matches!(
                component,
                std::path::Component::ParentDir
                    | std::path::Component::RootDir
                    | std::path::Component::Prefix(_)
            )
        })
    {
        bail!(
            "fleet task '{task_id}' {field} path '{}' must be one repo-relative line and cannot escape the workspace",
            path.display()
        );
    }
    let mut segments = Vec::new();
    for segment in raw.split('/') {
        match segment {
            "" | "." => {}
            ".." => {
                bail!(
                    "fleet task '{task_id}' {field} path '{}' cannot contain parent traversal",
                    path.display()
                );
            }
            value => segments.push(value),
        }
    }
    Ok(if segments.is_empty() {

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Express the path relative to the workspace root, e.g. "src" or "pkg-a/src".
  2. Remove drive letters, leading slashes, and any newline characters from the value.
  3. For paths outside the repo, use a coordination mechanism instead of an absolute writable path — the sandbox deliberately forbids escapes.

Example fix

# before
[workspace]
writable_paths = ["/home/me/repo/src", "C:\\work\\pkg"]

# after
[workspace]
writable_paths = ["src", "pkg"]
Defensive patterns

Strategy: validation

Validate before calling

fn is_safe_relative_path(raw: &str) -> bool {
    let normalized = raw.replace('\\', "/");
    !normalized.chars().any(|c| matches!(c, '\0' | '\r' | '\n'))
        && !normalized.starts_with('/')
        && !normalized.split('/').any(|seg| seg == ".." || seg.contains(':'))
        && !normalized.trim().is_empty()
}

Type guard

fn is_workspace_relative(path: &std::path::Path) -> bool {
    let raw = path.to_string_lossy().replace('\\', "/");
    !path.is_absolute() && !raw.split('/').any(|seg| seg == "..")
}

Prevention

When it happens

Trigger: `writable_paths = ["/tmp/work"]` (absolute), `["C:\\repo\\src"]` (Windows prefix), `["src\\nother"]` or a value containing a literal newline, or any entry with a `..` component.

Common situations: Authors pasting absolute output directories from their shell; specs written on Windows using backslash paths; templating that embeds multi-line values into a path field.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/ff0b552f1b5db428. Report an issue: GitHub.