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
- Include a supported read-only subcommand after any global flags, e.g. `git --no-pager diff` instead of `git --no-pager`
- Check the command string for quoting/truncation so all tokens survive shell_words::split
- 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
- Always pair global Git flags (--no-pager, -C) with an explicit subcommand
- Validate assembled command strings have a subcommand token before passing to the read-only shell
- Beware shell quoting that can silently drop trailing tokens
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
- classifier-approved Git read did not keep its subcommand in…
- allowlisted read-only executable
- no trusted executable search path remains outside the…
- pipeline contains a command outside the read-only policy
- read-only command must name a bare allowlisted executable
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)