Hmbown/CodeWhale · error · anyhow::Error

classifier-approved Git read was missing its literal subcomm

Error message

classifier-approved Git read was missing its literal subcommand

What it means

For `git` commands the hardener skips the admitted preamble flags (`--no-pager`, `-C <dir>`) and then requires a literal subcommand token at that position so it can splice subcommand-specific hardening flags (`--no-ext-diff`, `--no-textconv`, `--no-show-signature`). This error fires when the tokens end right after the preamble — no subcommand exists to harden — and the path fails closed.

Source

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

    if argv.first().is_some_and(|program| program == "git") {
        // The agent read-only classifier admits `git -C <dir>` and
        // `git --no-pager` before the subcommand; keep the preamble but
        // locate the subcommand after it so the hardening flags splice in
        // the right place. `-C` targets were already workspace-checked by
        // `enforce_readonly_workspace_operands`.
        let mut subcommand_index = 1;
        while let Some(flag) = argv.get(subcommand_index) {
            match flag.as_str() {
                "--no-pager" => subcommand_index += 1,
                "-C" => subcommand_index += 2,
                _ => break,
            }
        }
        let subcommand = argv
            .get(subcommand_index)
            .map(String::as_str)
            .ok_or_else(|| {
                anyhow!("classifier-approved Git read was missing its literal subcommand")
            })?;
        match subcommand {
            "diff" => {
                let at = subcommand_index + 1;
                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(),
                    ],

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Always emit a concrete git subcommand after any `-C <dir>` / `--no-pager` preamble (e.g. `git -C repo status`)
  2. Validate that commands starting with `git` have at least two non-preamble tokens before dispatch
  3. Route partial git invocations through a full-permission shell call with approval instead

Example fix

# before: preamble only, subcommand missing
git -C repo
# after: preamble plus a hardened read subcommand
git -C repo status
Defensive patterns

Strategy: validation

Validate before calling

fn git_has_literal_subcommand(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,
        }
    }
    argv.get(i).is_some()
}

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("missing its literal subcommand") => {
        report("emit a concrete git subcommand after -C/--no-pager and retry")
    }
    Err(err) => Err(err),
}

Prevention

When it happens

Trigger: A classifier-admitted git invocation whose token list is only preamble: `git -C repo` (the `-C` consumed `repo` as its value, leaving nothing) or `git --no-pager` with nothing following; typically a concatenation bug where the subcommand variable was empty.

Common situations: Commands assembled by string interpolation with an empty subcommand; a `-C` argument that accidentally swallowed the only remaining token.

Related errors


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