{"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":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/shell.rs","lineNumber":4188,"sourceCode":"            let program = resolve_readonly_program(&program, workspace)?;\n            let program = program\n                .to_str()\n                .ok_or_else(|| anyhow!(\"read-only executable path is not valid UTF-8\"))?;\n            Ok(std::iter::once(program)\n                .chain(args.iter().map(String::as_str))\n                .map(|arg| shell_words::quote(arg).into_owned())\n                .collect::<Vec<_>>()\n                .join(\" \"))\n        })\n        .collect::<Result<Vec<_>>>()?;\n    // The only shell operators are our pipes. Filenames cannot expand into\n    // options or unvalidated symlinks; every Git stage retains helper guards.\n    Ok(format!(\"set -o pipefail; {}\", segments.join(\" | \")))\n}\n\nfn hardened_readonly_argv(command: &str) -> Result<(String, Vec<String>)> {\n    let mut argv = shell_words::split(&normalize_windows_command_paths(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":4170,"sourceCodeEnd":4206,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/tools/shell.rs#L4170-L4206","documentation":"hardened_readonly_argv tokenizes the classifier-approved command using shell_words::split (POSIX shell word splitting) after Windows path normalization. If the command string is not valid POSIX shell syntax for splitting (unbalanced quotes, trailing backslash, etc.), the tool cannot safely decompose it into argv and rejects it.","triggerScenarios":"A pipeline segment contains shell syntax shell_words::split rejects: unbalanced single/double quotes, an unterminated escape like 'foo\\', or other malformed quoting in the command the classifier approved.","commonSituations":"The agent model emits a command with a stray apostrophe (e.g. git log --format='%h | it's fine'); generated commands interpolating user text with quotes; Windows-style paths with backslashes misinterpreted as escapes.","solutions":["Re-issue the command with balanced, properly escaped quoting — wrap arguments containing spaces or special characters in double quotes.","Remove problematic characters (apostrophes, trailing backslashes) from arguments, or use simpler glob-free arguments.","If this comes from model output, retry the request so the model regenerates a syntactically valid command."],"exampleFix":"// before (unbalanced quote)\ngit log --format='%h %s it's a commit | head\n// after\ngit log --format=\"%h %s\" | head","handlingStrategy":"validation","validationCode":"function quotesBalanced(cmd: string): boolean {\n  let s = false, d = false;\n  for (let i = 0; i < cmd.length; i++) {\n    const c = cmd[i];\n    if (c === '\\\\') { i++; continue; }\n    if (c === \"'\" && !d) s = !s;\n    if (c === '\"' && !s) d = !d;\n  }\n  return !s && !d && !cmd.trimEnd().endsWith('\\\\');\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Prefer double quotes around arguments that contain apostrophes or spaces.","Avoid trailing backslashes and unbalanced quotes in generated commands.","Sanitize interpolated user text before embedding it in command strings."],"tags":["shell","parse-error","quoting","pipeline"],"backgroundTag":"invalid-argument-format","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}