Hmbown/CodeWhale · error · anyhow::Error

classifier-approved Git read did not keep its subcommand in

Error message

classifier-approved Git read did not keep its subcommand in argv[1]

What it means

After skipping the admitted preamble, the hardener only knows how to harden git's read subcommands: `diff`, `log`, `show` (get flag splices) plus `status`, `ls-files`, `blame`, `grep` (pass through bare). Any other subcommand reaches this refusal — the classifier admitted something the hardener cannot prove safe, so the path fails closed. The message text says 'argv[1]' but the check runs at the computed subcommand position after `-C`/`--no-pager`.

Source

Thrown at crates/tui/src/tools/shell.rs:3768

                argv.splice(
                    at..at,
                    ["--no-ext-diff".to_string(), "--no-textconv".to_string()],
                );
            }
            "log" | "show" => {
                let at = subcommand_index + 1;
                argv.splice(
                    at..at,
                    [
                        "--no-ext-diff".to_string(),
                        "--no-textconv".to_string(),
                        "--no-show-signature".to_string(),
                    ],
                );
            }
            "status" | "ls-files" | "blame" | "grep" => {}
            _ => {
                return Err(anyhow!(
                    "classifier-approved Git read did not keep its subcommand in argv[1]"
                ));
            }
        }
    }

    let program = argv.remove(0);
    Ok((program, argv))
}

fn enforce_readonly_workspace_operands(
    command: &str,
    workspace: &std::path::Path,
    effective_cwd: &std::path::Path,
) -> Result<(), ToolError> {
    let argv = shell_words::split(command).map_err(|error| {
        ToolError::invalid_input(format!(
            "Could not parse read-only command arguments: {error}"

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Restrict agent git reads to the hardened set: diff/log/show/status/ls-files/blame/grep
  2. Route other git reads through a normal (approved, non-readonly) shell call or the File tools
  3. Report the admitted-but-unhardened subcommand as a classifier/hardener mismatch bug
  4. For `remote -v` style info, prefer `gh` reads or configuration inspection via allowed tools

Example fix

# before: subcommand outside the hardened set
git -C repo remote -v
# after: hardened read, or an approved full shell call
git -C repo status   # or dispatch 'git remote -v' through the non-readonly path
Defensive patterns

Strategy: validation

Validate before calling

const HARDENED_GIT_READS: &[&str] = &["diff", "log", "show", "status", "ls-files", "blame", "grep"];

fn git_subcommand_if_any(argv: &[String]) -> Option<&str> {
    if argv.first().map(String::as_str) != Some("git") {
        return None;
    }
    let mut i = 1;
    while let Some(flag) = argv.get(i) {
        match flag.as_str() {
            "--no-pager" => i += 1,
            "-C" => i += 2,
            _ => break,
        }
    }
    argv.get(i).map(String::as_str)
}

if let Some(sub) = git_subcommand_if_any(&argv)
    && !HARDENED_GIT_READS.contains(&sub)
{
    return report(format!("git {sub} is outside the hardened read set; use an approved full shell call"));
}

Type guard

fn hardenable_git_read(command: &str) -> bool {
    let Ok(argv) = shell_words::split(command) else { return false };
    if argv.first().map(String::as_str) != Some("git") { return true; }
    let mut i = 1;
    while let Some(flag) = argv.get(i) {
        match flag.as_str() {
            "--no-pager" => i += 1,
            "-C" => i += 2,
            _ => break,
        }
    }
    matches!(
        argv.get(i).map(String::as_str),
        Some("diff" | "log" | "show" | "status" | "ls-files" | "blame" | "grep")
    )
}

Try / catch

match hardened_readonly_argv(command) {
    Ok(parsed) => Ok(parsed),
    Err(err) if err.to_string().contains("did not keep its subcommand") => {
        report("switch to a hardened git read (diff/log/show/status/ls-files/blame/grep) or use an approved full shell call")
    }
    Err(err) => Err(err),
}

Prevention

When it happens

Trigger: A classifier-admitted git read whose subcommand falls outside {diff, log, show, status, ls-files, blame, grep} — e.g. `git -C dir remote -v`, `git describe --tags`, `git shortlog` — reaching the match's `_` arm at shell.rs:3768.

Common situations: Classifier allowlist drift admitting new git subcommands; agents probing less-common read-only git verbs that look harmless.

Related errors


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