{"record":{"id":"f4bf0df5be281bb0","repo":"Hmbown/CodeWhale","slug":"classifier-approved-read-command-was-empty","errorCode":null,"errorMessage":"classifier-approved read command was empty","messagePattern":"classifier-approved read command was empty","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/tools/shell.rs","lineNumber":3722,"sourceCode":"    }\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,\n            }\n        }","sourceCodeStart":3704,"sourceCodeEnd":3740,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/tools/shell.rs#L3704-L3740","documentation":"`hardened_readonly_argv` requires at least one token after `shell_words` splitting. Zero tokens means the command was empty, whitespace-only, or only a shell comment (shell_words strips `#` comments by default). The readonly classifier is expected to reject such input upstream, so reaching this error means an effectively-empty command slipped through classification — the path fails closed rather than executing anything.","triggerScenarios":"Dispatching an exec_shell 'run' action whose `command` is `\"\"`, `\"   \"`, or `\"# just a comment\"` through the read-only (Scout/Reviewer) policy branch without a pipe.","commonSituations":"Template bugs that interpolate an empty variable into the command string; harness probes testing the readonly gate with blank input; comment-only placeholder commands.","solutions":["Skip execution entirely when the command trims to empty or starts with '#'","Validate a non-empty command field at the tool-input schema before dispatch","If a non-empty command still reaches this check, the comment-stripping behavior is the likely cause — remove leading '#' commentary and retry","Report as a classifier gap if a meaningful command was intended"],"exampleFix":"# before: comment-only command dispatched to the read-only path\n{\"action\": \"run\", \"command\": \"# check status\"}\n# after: a real read command\n{\"action\": \"run\", \"command\": \"git status\"}","handlingStrategy":"validation","validationCode":"let trimmed = command.trim();\nif trimmed.is_empty() || trimmed.starts_with('#') {\n    return report(\"skip execution: command is empty or comment-only\");\n}","typeGuard":"fn nonempty_readonly_command(command: &str) -> bool {\n    shell_words::split(command).map(|argv| !argv.is_empty()).unwrap_or(false)\n}","tryCatchPattern":"match hardened_readonly_argv(command) {\n    Ok(parsed) => Ok(parsed),\n    Err(err) if err.to_string().contains(\"was empty\") => {\n        Ok(skip_silently(\"nothing to execute\"))\n    }\n    Err(err) => Err(err),\n}","preventionTips":["Reject empty/whitespace command fields at the tool-input schema","Remember shell_words strips `# comment` lines — comment-only strings split to zero tokens","Guard template interpolation so empty variables cannot blank a command","Never submit placeholder commands to the read-only path"],"tags":["readonly-shell","empty-input","validation","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"}