Hmbown/CodeWhale · error

unexpected patch directive: {raw_line}

Error message

unexpected patch directive: {raw_line}

What it means

Inside the patch body only `@@` hunk markers and ` `/`-`/`+` lines are legal. Any other line starting with `*** ` — a second `*** Update File:` section, an `*** Add File:` section, or an `*** End of File` sentinel — is rejected as an unexpected directive (eval.rs:704).

Source

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

    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;
        }

        let (kind, rest) = raw_line.split_at(1);
        let content = rest.to_string();

        match kind {
            " " => {
                let Some(found) = file_lines[cursor..]
                    .iter()
                    .position(|line| line == &content)
                    .map(|offset| cursor + offset)
                else {
                    return Err(anyhow!(
                        "patch context not found in {}: {}",
                        file_path.display(),

View on GitHub (pinned to 8880682c63)

Solutions

  1. Split into one patch per file, each with its own Begin/Update/End structure
  2. Strip `*** End of File` and other sentinels before applying
  3. Validate body lines before apply_patch runs

Example fix

// before: apply raw model output
apply_patch(&root, &raw)?;
// after: strip unsupported sentinels first
let cleaned: String = raw.lines()
    .filter(|l| !l.starts_with("*** End of File"))
    .collect::<Vec<_>>().join("\n");
apply_patch(&root, &cleaned)?;
Defensive patterns

Strategy: validation

Validate before calling

for line in patch.lines().skip(2) {
    if line == "*** End Patch" {
        break;
    }
    anyhow::ensure!(
        !line.starts_with("*** "),
        "unexpected patch directive: {line}"
    );
}

Type guard

fn patch_body_is_clean(patch: &str) -> bool {
    patch.lines()
        .skip(2)
        .take_while(|l| *l != "*** End Patch")
        .all(|l| !l.starts_with("*** "))
}

Prevention

When it happens

Trigger: A multi-file or multi-section patch; an OpenAI-style `*** End of File` marker appended to hunks; stray `***` comment lines inside the body.

Common situations: Model batches several file edits into one patch; dialect mixing between patch formats.

Related errors


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