Hmbown/CodeWhale · error

classifier-approved Git read was missing its literal…

Error message

classifier-approved Git read was missing its literal subcommand

What it means

This error is thrown by `hardened_readonly_argv` in the read-only shell hardening path. When a classifier-approved read command starts with `git`, the hardener walks past the known preamble flags `--no-pager` and `-C <dir>` to locate the Git subcommand, then splices safety flags after it. If the argv ends before a subcommand is found (only preamble flags were supplied), the hardener refuses to proceed rather than guess what Git should run.

Solutions

  1. Include a supported read-only subcommand after any global flags, e.g. `git --no-pager diff` instead of `git --no-pager`
  2. Check the command string for quoting/truncation so all tokens survive shell_words::split
  3. If a new global Git flag must be supported, extend the preamble-skip match in hardened_readonly_argv so the subcommand index lands on the real subcommand

Example fix

// before
run_readonly("git --no-pager")
// after
run_readonly("git --no-pager diff")
Defensive patterns

Strategy: validation

Validate before calling

fn has_git_subcommand(cmd: &str) -> bool {
    let argv = shell_words::split(cmd).unwrap_or_default();
    if argv.first().map(String::as_str) != Some("git") { return true; }
    let mut i = 1;
    while matches!(argv.get(i).map(String::as_str), Some("--no-pager") | Some("-C")) {
        i += if argv.get(i).map(String::as_str) == Some("-C") { 2 } else { 1 };
    }
    argv.get(i).is_some()
}
if !has_git_subcommand(&cmd) { eprintln!("git command needs a subcommand"); }

Prevention

When it happens

Trigger: Calling the read-only shell path with a command like `git --no-pager` or `git -C /some/dir` where tokenizing stops after the preamble flags and no subcommand (diff/log/show/status/ls-files/blame/grep) follows. Also produced if shell_words parsing yields fewer tokens than expected after path normalization.

Common situations: Programmatically assembled Git commands that end after global flags; a command string truncated by a quoting bug so `git -C ..` loses its subcommand; classifier policy updated to admit new preamble flags without extending the skip loop, so the loop consumes everything and leaves nothing at subcommand_index.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


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

Appendix: source

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

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