Hmbown/CodeWhale · warning
shell commands are restricted by
Error message
shell commands are restricted by {} What it means
Shell command execution is gated by a ShellAccessControl policy (ProjectConfig, Profile, Environment, ManagedConfig, Ambiguous). When the resolved policy denies shell access, the worker bails with the policy label so the user knows which control blocked the command. This is deliberate permission enforcement, not a bug.
Solutions
- Enable shell in the owning configuration (project config, profile, or environment) that the label names
- Check config.allow_shell() and the ShellAccessControl resolution for the thread before invoking shell tools
- If ManagedConfig blocks it, contact the administrator managing that config
Example fix
// before (fails under Profile policy with allow_shell=false)
runtime.run_shell(thread_id, "cargo test").await?;
// after
if runtime.shell_policy_allows(thread_id).await {
runtime.run_shell(thread_id, "cargo test").await?;
} Defensive patterns
Strategy: try-catch
Validate before calling
if !config.allow_shell() { return Err(anyhow!("shell disabled by policy")); } Try / catch
match runtime.run_shell(thread_id, cmd).await {
Err(e) if e.to_string().starts_with("shell commands are restricted by") => {
eprintln!("Enable shell in {}: {}", e, hint);
}
other => other?,
} Prevention
- Check config.allow_shell() before issuing shell commands
- Surface the policy label in UI before running shell tools
- Document which config source (profile/env/managed) controls shell
When it happens
Trigger: Invoking a shell tool on a thread whose effective shell policy resolves to Profile, Environment, or ManagedConfig while config.allow_shell() is false (or policy is Ambiguous with default-deny).
Common situations: Fresh checkout where the project config has shell disabled; enterprise-managed config restricting shell; profile selected without shell permission; ambiguous settings after config migration.
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…
- legacy spillover ownership sidecar must not be a symlink
- no trusted executable search path remains outside the…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/043a3a15f860d70a.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:11216
#[cfg(test)]
let env_ticket = crate::test_support::env_scope_ticket();
tokio::task::spawn_blocking(move || {
#[cfg(test)]
let _membership = crate::test_support::join_env_scope(env_ticket);
let control = config.allow_shell_control(
config_path.as_deref(),
config_profile.as_deref(),
&workspace,
);
let denied = match control {
ShellAccessControl::Unset | ShellAccessControl::RootConfig => false,
ShellAccessControl::ProjectConfig | ShellAccessControl::Ambiguous => true,
ShellAccessControl::Profile
| ShellAccessControl::Environment
| ShellAccessControl::ManagedConfig => !config.allow_shell(),
};
if denied {
bail!("shell commands are restricted by {}", control.label());
}
Ok(())
})
.await
.context("shell policy inspection worker failed")?
}
/// The sandbox policy an API-created job inherits — the same posture
/// projection a turn applies to its shell calls, so a client terminal
/// cannot run looser than the thread's own tools would.
pub(crate) async fn thread_job_sandbox_policy(
&self,
thread: &ThreadRecord,
) -> crate::sandbox::SandboxPolicy {
let policy = RuntimePolicyProjection::from_persisted(
&thread.mode,
thread.permission_posture.as_deref(),
thread.auto_approve,View on GitHub (pinned to 73e0f67d83)