Hmbown/CodeWhale · error

patch path must be workspace-relative

Error message

patch path must be workspace-relative

What it means

The path after `*** Update File:` must stay inside the eval workspace: any occurrence of `..` in the relative path is rejected before the file is read (eval.rs:690). This is the sandbox-escape guard for patch targets.

Source

Thrown at crates/tui/src/eval.rs:690

    content.push_str(line);
    content.push('\n');
    fs::write(path, content).with_context(|| format!("failed to write {}", path.display()))
}

fn apply_patch(root: &Path, patch: &str) -> Result<()> {
    let mut lines = patch.lines();

    let begin = lines.next().unwrap_or_default();
    if begin != "*** Begin Patch" {
        return Err(anyhow!("patch missing *** Begin Patch header"));
    }

    let header = lines.next().unwrap_or_default();
    let file_rel = header
        .strip_prefix("*** Update File: ")
        .ok_or_else(|| anyhow!("only *** Update File patches are supported"))?;
    if file_rel.contains("..") {
        return Err(anyhow!("patch path must be workspace-relative"));
    }

    let file_path = root.join(file_rel);
    let original = read_workspace_file(&file_path)?;
    let had_trailing_newline = original.ends_with('\n');
    let mut file_lines: Vec<String> = original.lines().map(|l| l.to_string()).collect();

    let mut cursor = 0usize;
    for raw_line in lines {
        if raw_line == "*** End Patch" {
            break;
        }
        if raw_line.starts_with("*** ") {
            return Err(anyhow!("unexpected patch directive: {raw_line}"));
        }
        if raw_line.starts_with("@@") {
            continue;
        }

View on GitHub (pinned to 8880682c63)

Solutions

  1. Move the target file inside the workspace, or reconfigure the eval root so the target is contained
  2. Emit a path relative to the workspace root with no parent-directory segments

Example fix

*** before
*** Update File: ../shared/config.toml
*** after (root the workspace one level up, or reference an in-workspace copy)
*** Update File: shared/config.toml
Defensive patterns

Strategy: validation

Validate before calling

let rel = header
    .strip_prefix("*** Update File: ")
    .context("not an update directive")?;
anyhow::ensure!(!rel.contains(".."), "patch escapes workspace: {rel}");

Type guard

fn patch_path_is_in_workspace(rel: &str) -> bool {
    !rel.contains("..")
}

Prevention

When it happens

Trigger: The patch references `../outside.txt`, `a/../../b`, or any parent traversal; the model tries to edit a file that lives above the eval workspace root.

Common situations: Model reasons about absolute repo paths and emits them as relative paths with `..`; eval root configured one level below the files the model was shown.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@8880682c63 (2026-08-16). Data as JSON: /api/errors/5ce29c8a96d86a60. Report an issue: GitHub.