{"record":{"id":"9378c0e23eb42122","repo":"Hmbown/CodeWhale","slug":"could-not-parse-classifier-approved-read-command","errorCode":null,"errorMessage":"could not parse classifier-approved read command: {error}","messagePattern":"could not parse classifier-approved read command: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/shell.rs","lineNumber":3720,"sourceCode":"    {\n        return false;\n    }\n    if [\"task_id\", \"id\", \"wait\", \"block\", \"close_stdin\", \"all\"]\n        .iter()\n        .any(|key| input.get(*key).is_some())\n    {\n        return false;\n    }\n\n    input\n        .get(\"command\")\n        .and_then(serde_json::Value::as_str)\n        .is_some()\n}\n\nfn hardened_readonly_argv(command: &str) -> Result<(String, Vec<String>)> {\n    let mut argv = shell_words::split(command)\n        .map_err(|error| anyhow!(\"could not parse classifier-approved read command: {error}\"))?;\n    if argv.is_empty() {\n        return Err(anyhow!(\"classifier-approved read command was empty\"));\n    }\n\n    // Even when repository/user configuration names a diff or signature\n    // helper, these flags make Git keep the read inside its own process.\n    if argv.first().is_some_and(|program| program == \"git\") {\n        // The agent read-only classifier admits `git -C <dir>` and\n        // `git --no-pager` before the subcommand; keep the preamble but\n        // locate the subcommand after it so the hardening flags splice in\n        // the right place. `-C` targets were already workspace-checked by\n        // `enforce_readonly_workspace_operands`.\n        let mut subcommand_index = 1;\n        while let Some(flag) = argv.get(subcommand_index) {\n            match flag.as_str() {\n                \"--no-pager\" => subcommand_index += 1,\n                \"-C\" => subcommand_index += 2,\n                _ => break,","sourceCodeStart":3702,"sourceCodeEnd":3738,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/tools/shell.rs#L3702-L3738","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before: unterminated quote admitted by the classifier\ngit log --grep=\"open\n# after: balanced quoting\ngit log --grep=\"open\"","handlingStrategy":"validation","validationCode":"fn tokenize_cleanly(command: &str) -> bool {\n    shell_words::split(command).is_ok()\n}\n\nif !tokenize_cleanly(command) {\n    return report(format!(\"fix the quoting in: {command}\"));\n}","typeGuard":"fn parseable_readonly_command(command: &str) -> bool {\n    shell_words::split(command).map(|argv| !argv.is_empty()).unwrap_or(false)\n}","tryCatchPattern":"let (program, args) = match hardened_readonly_argv(command) {\n    Ok(parsed) => parsed,\n    Err(err) if err.to_string().starts_with(\"could not parse\") => {\n        return report(format!(\"re-quote the command and retry: {command}\"));\n    }\n    Err(err) => return Err(err),\n};","preventionTips":["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"],"tags":["readonly-shell","shell-parsing","quoting","security","rust"],"backgroundTag":"shell-command-parse-error","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T13:17:26.733Z"}