Hmbown/CodeWhale · error

classifier-approved read command was empty

Error message

classifier-approved read command was empty

What it means

After shell_words parsing succeeds, hardened_readonly_argv rejects an empty argv. A command that tokenizes to zero words carries no program to execute, so the read-only pipeline builder cannot construct a stage for it and fails loudly rather than emitting a meaningless empty command.

Solutions

  1. Supply a non-empty read-only command (e.g. 'git status', 'ls').
  2. Fix doubled or trailing pipe characters in the pipeline so no segment is empty ('a | b', not 'a | | b').
  3. If the model produced a blank command, retry the request to get a real command.

Example fix

// before
"git log | | head"   // middle segment is empty
// after
"git log | head"
Defensive patterns

Strategy: validation

Validate before calling

function hasEmptySegment(cmd: string): boolean {
  if (!cmd.trim()) return true;
  return cmd.split('|').some(seg => seg.trim() === '');
}

Prevention

When it happens

Trigger: Passing an empty string, a whitespace-only string, or a segment that reduces to nothing (e.g. a pipeline like 'a | | b' producing an empty segment, or a comment-only string like '# nothing') as a classifier-approved read command.

Common situations: Malformed pipelines with doubled pipes creating empty segments; the agent model emitting blank or whitespace-only commands; command strings that consist solely of quotes stripped away during tokenization.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/f4bf0df5be281bb0. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tools/shell.rs:4190

                .to_str()
                .ok_or_else(|| anyhow!("read-only executable path is not valid UTF-8"))?;
            Ok(std::iter::once(program)
                .chain(args.iter().map(String::as_str))
                .map(|arg| shell_words::quote(arg).into_owned())
                .collect::<Vec<_>>()
                .join(" "))
        })
        .collect::<Result<Vec<_>>>()?;
    // The only shell operators are our pipes. Filenames cannot expand into
    // options or unvalidated symlinks; every Git stage retains helper guards.
    Ok(format!("set -o pipefail; {}", segments.join(" | ")))
}

fn hardened_readonly_argv(command: &str) -> Result<(String, Vec<String>)> {
    let mut argv = shell_words::split(&normalize_windows_command_paths(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 73e0f67d83)