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
- Remove or replace the non-read-only command in the pipeline so every stage is an allowed read-only program.
- Replace output capture (tee, >, >>) with a plain read that prints to stdout.
- 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
- Keep agent prompts instructing output-only reads; forbid tee and redirect operators in read mode.
- Review generated pipelines for write commands before approving them.
- Keep the read-only allowlist explicit; don't rely on classifier approval alone.
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
- allowlisted read-only executable
- classifier-approved Git read did not keep its subcommand in…
- classifier-approved Git read was missing its literal…
- classifier-approved read command was empty
- could not parse classifier-approved read command
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)