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
- Always emit a concrete git subcommand after any `-C <dir>` / `--no-pager` preamble (e.g. `git -C repo status`)
- Validate that commands starting with `git` have at least two non-preamble tokens before dispatch
- 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
- Always emit a subcommand after `git -C <dir>` — the flag consumes the next token
- Validate that git commands have a token beyond the preamble before dispatch
- Assemble git commands from non-empty parts; check interpolated subcommand variables
- Route partial invocations through an approved full shell call
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
- classifier-approved Git read did not keep its subcommand in
- could not parse classifier-approved read command: {error}
- classifier-approved read command was empty
- read-only command must name a bare allowlisted executable
- no trusted executable search path remains outside the worksp
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/f20ce19f1b3cc843.
Report an issue: GitHub.