Hmbown/CodeWhale · error

Fleet task ' ' path ' ' must be one repo-relative line and…

Error message

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

What it means

normalize_fleet_relative_path rejects a declared Fleet write path that is not a clean repo-relative path: if the raw path contains a ParentDir, RootDir, or Prefix component (absolute path, drive prefix, or leading `..`), it cannot be contained inside the workspace and the task fails. Write claims must each be a single repo-relative line so the runtime can bound them under the workspace root.

Solutions

  1. Replace the absolute path with a path relative to the repository root (e.g. `src/module/`).
  2. Remove any `..` traversal; enumerate the actual in-repo directories you need instead.
  3. Strip drive-letter/UNC prefixes from Windows-style entries and re-express them relative to the workspace.
  4. If you truly need out-of-workspace writes, restructure the task or workspace rather than escaping the claim.

Example fix

// before
writable_paths = ["/home/me/repo/src", "../shared"]

// after
writable_paths = ["src"]
Defensive patterns

Strategy: validation

Validate before calling

fn is_clean_relative(p: &Path) -> bool {
    !p.is_absolute()
        && p.components().all(|c| !matches!(c, Component::ParentDir | Component::RootDir | Component::Prefix(_)))
}

Prevention

When it happens

Trigger: fleet_write_roots or fleet_runtime_write_roots calls normalize_fleet_relative_path with a path like `/etc/passwd`, `C:\repo\src`, `../sibling/`, or `../../out`, producing this error with field naming which claim (writable_paths or runtime write root) was rejected.

Common situations: Pasting an absolute path from the host into writable_paths; using `..` to reach a sibling repo from a fleet task; Windows-authored configs with drive-letter prefixes checked into a repo used on Linux.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/971081540886ae97. Report an issue: GitHub.

Appendix: source

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

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 73e0f67d83)