Hmbown/CodeWhale · error · anyhow::Error
could not parse classifier-approved read command: {error}
Error message
could not parse classifier-approved read command: {error} What it means
On the agent read-only path (ShellPolicy::ReadOnly), a command first passes the classifier `is_agent_readonly_shell_command`, then `hardened_readonly_argv` re-tokenizes it with the `shell_words` crate to build a literal argv (shell.rs:2008). This error means `shell_words::split` rejected the string — typically an unmatched quote or dangling escape — i.e. the classifier's tokenizer and the crate disagree. It is a fail-closed defense-in-depth check: the command never runs.
Source
Thrown at crates/tui/src/tools/shell.rs:3720
{
return false;
}
if ["task_id", "id", "wait", "block", "close_stdin", "all"]
.iter()
.any(|key| input.get(*key).is_some())
{
return false;
}
input
.get("command")
.and_then(serde_json::Value::as_str)
.is_some()
}
fn hardened_readonly_argv(command: &str) -> Result<(String, Vec<String>)> {
let mut argv = shell_words::split(command)
.map_err(|error| anyhow!("could not parse classifier-approved read command: {error}"))?;
if argv.is_empty() {
return Err(anyhow!("classifier-approved read command was empty"));
}
// Even when repository/user configuration names a diff or signature
// helper, these flags make Git keep the read inside its own process.
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,View on GitHub (pinned to 0c42157ee5)
Solutions
- Fix the quoting in the command (balance single/double quotes, remove stray backslashes) and retry
- Simplify to a plainly-quoted, pipeline-free invocation the hardener can tokenize
- If the command looks syntactically valid, report the classifier/hardener mismatch as a bug against the shell tool
- Use the bounded File read/search tools for patterns that are awkward to quote
Example fix
# before: unterminated quote admitted by the classifier git log --grep="open # after: balanced quoting git log --grep="open"
Defensive patterns
Strategy: validation
Validate before calling
fn tokenize_cleanly(command: &str) -> bool {
shell_words::split(command).is_ok()
}
if !tokenize_cleanly(command) {
return report(format!("fix the quoting in: {command}"));
} Type guard
fn parseable_readonly_command(command: &str) -> bool {
shell_words::split(command).map(|argv| !argv.is_empty()).unwrap_or(false)
} Try / catch
let (program, args) = match hardened_readonly_argv(command) {
Ok(parsed) => parsed,
Err(err) if err.to_string().starts_with("could not parse") => {
return report(format!("re-quote the command and retry: {command}"));
}
Err(err) => return Err(err),
}; Prevention
- Lint generated commands for balanced quotes and no trailing backslashes
- Pre-split commands with the same shell_words rules before dispatch
- Prefer simple quoting; avoid clever escapes in read-only commands
- Treat a parse failure on a valid-looking command as a classifier mismatch worth reporting
When it happens
Trigger: Executing a classifier-admitted, pipeline-free read command containing an unbalanced quote or trailing backslash (e.g. `rg 'pattern` or `git log --grep="foo`), which reaches the `hardened_readonly_argv` call in the non-pipe branch of exec.
Common situations: Model-generated commands with mangled quoting that still pass the classifier's charset/token filters; drift between the classifier's own tokenizer and the shell_words crate after an upgrade.
Related errors
- classifier-approved read command was empty
- classifier-approved Git read was missing its literal subcomm
- classifier-approved Git read did not keep its subcommand in
- 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/9378c0e23eb42122.
Report an issue: GitHub.