Hmbown/CodeWhale · error

pipeline contains a command outside the read-only policy

Error message

pipeline contains a command outside the read-only policy

What it means

Before executing a pipeline the shell tool re-validates the whole command string with is_agent_readonly_shell_command. If any part of the classifier-approved command is outside the read-only policy (a non-read program, redirection to a write, etc.), execution is refused. This is a defense-in-depth check run after the classifier so nothing non-read-only reaches the shell.

Solutions

  1. Remove or replace the non-read-only command in the pipeline so every stage is an allowed read-only program.
  2. Replace output capture (tee, >, >>) with a plain read that prints to stdout.
  3. If a legitimate read-only helper is being rejected, update the read-only policy/allowlist configuration to include it explicitly.

Example fix

// before (rejected: tee writes)
agent: cat config.toml | tee /tmp/backup.toml
// after
agent: cat config.toml
Defensive patterns

Strategy: validation

Validate before calling

const WRITE_CMDS = new Set(['rm','mv','cp','tee','mkdir','touch','chmod','dd','shred',']]);
function isReadOnlyPipeline(cmd: string): boolean {
  return cmd.split('|').every(seg => {
    const prog = seg.trim().split(/\s+/)[0]?.replace(/^env\s+\S+\s+/, '') ?? '';
    return !prog.includes('>') && !WRITE_CMDS.has(prog);
  });
}

Prevention

When it happens

Trigger: Submitting a pipeline (via the agent shell tool with read-only mode active) whose command string fails is_agent_readonly_shell_command — e.g. it contains a write command (rm, mv, tee, redirect '>'), or an unknown program the policy does not whitelist.

Common situations: The agent model proposes 'git status | tee out.txt'; a user-configured git helper or alias expands to something non-read-only; a pipeline includes curl/wget or shell builtins the policy doesn't recognize.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


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

Appendix: source

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

    use crate::shell_dispatcher::ShellKind;
    // POSIX quoting must never be passed to a different command interpreter.
    let supported = match crate::shell_dispatcher::global_dispatcher().kind() {
        ShellKind::Bash => true,
        ShellKind::Custom { binary, .. } => matches!(
            std::path::Path::new(binary)
                .file_name()
                .and_then(|name| name.to_str()),
            Some("bash" | "zsh")
        ),
        _ => false,
    };
    if !supported {
        return Err(anyhow!(
            "read-only pipelines require bash or zsh; run each read separately"
        ));
    }
    if !is_agent_readonly_shell_command(command) {
        return Err(anyhow!(
            "pipeline contains a command outside the read-only policy"
        ));
    }
    let segments = command
        .split('|')
        .map(|segment| {
            let (program, args) = hardened_readonly_argv(segment)?;
            let program = resolve_readonly_program(&program, workspace)?;
            let program = program
                .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<_>>>()?;

View on GitHub (pinned to 73e0f67d83)