diff --git a/codex-rs/core/src/tasks/user_shell.rs b/codex-rs/core/src/tasks/user_shell.rs index 0e57e1b728..eddfe00511 100644 --- a/codex-rs/core/src/tasks/user_shell.rs +++ b/codex-rs/core/src/tasks/user_shell.rs @@ -89,7 +89,10 @@ impl SessionTask for UserShellCommandTask { let tool_call = ToolCall { tool_name: USER_SHELL_TOOL_NAME.to_string(), call_id: Uuid::new_v4().to_string(), - payload: ToolPayload::LocalShell { params }, + payload: ToolPayload::LocalShell { + params, + is_user_shell_command: true, + }, }; let router = Arc::new(ToolRouter::from_config(&turn_context.tools_config, None)); diff --git a/codex-rs/core/src/tools/context.rs b/codex-rs/core/src/tools/context.rs index 029bacaa35..914617ec11 100644 --- a/codex-rs/core/src/tools/context.rs +++ b/codex-rs/core/src/tools/context.rs @@ -40,6 +40,7 @@ pub enum ToolPayload { }, LocalShell { params: ShellToolCallParams, + is_user_shell_command: bool, }, UnifiedExec { arguments: String, @@ -56,7 +57,7 @@ impl ToolPayload { match self { ToolPayload::Function { arguments } => Cow::Borrowed(arguments), ToolPayload::Custom { input } => Cow::Borrowed(input), - ToolPayload::LocalShell { params } => Cow::Owned(params.command.join(" ")), + ToolPayload::LocalShell { params, .. } => Cow::Owned(params.command.join(" ")), ToolPayload::UnifiedExec { arguments } => Cow::Borrowed(arguments), ToolPayload::Mcp { raw_arguments, .. } => Cow::Borrowed(raw_arguments), } diff --git a/codex-rs/core/src/tools/handlers/shell.rs b/codex-rs/core/src/tools/handlers/shell.rs index b97242a9a3..0cbe726290 100644 --- a/codex-rs/core/src/tools/handlers/shell.rs +++ b/codex-rs/core/src/tools/handlers/shell.rs @@ -82,7 +82,10 @@ impl ToolHandler for ShellHandler { ) .await } - ToolPayload::LocalShell { params } => { + ToolPayload::LocalShell { + params, + is_user_shell_command, + } => { let exec_params = Self::to_exec_params(params, turn.as_ref()); Self::run_exec_like( tool_name.as_str(), @@ -91,7 +94,7 @@ impl ToolHandler for ShellHandler { turn, tracker, call_id, - true, + is_user_shell_command, ) .await } @@ -219,6 +222,7 @@ impl ShellHandler { env: exec_params.env.clone(), with_escalated_permissions: exec_params.with_escalated_permissions, justification: exec_params.justification.clone(), + is_user_shell_command, }; let mut orchestrator = ToolOrchestrator::new(); let mut runtime = ShellRuntime::new(); diff --git a/codex-rs/core/src/tools/router.rs b/codex-rs/core/src/tools/router.rs index 19098aa80d..e301289069 100644 --- a/codex-rs/core/src/tools/router.rs +++ b/codex-rs/core/src/tools/router.rs @@ -120,7 +120,10 @@ impl ToolRouter { Ok(Some(ToolCall { tool_name: "local_shell".to_string(), call_id, - payload: ToolPayload::LocalShell { params }, + payload: ToolPayload::LocalShell { + params, + is_user_shell_command: false, + }, })) } } diff --git a/codex-rs/core/src/tools/runtimes/shell.rs b/codex-rs/core/src/tools/runtimes/shell.rs index f29224fcc1..8c31526699 100644 --- a/codex-rs/core/src/tools/runtimes/shell.rs +++ b/codex-rs/core/src/tools/runtimes/shell.rs @@ -34,6 +34,7 @@ pub struct ShellRequest { pub env: std::collections::HashMap, pub with_escalated_permissions: Option, pub justification: Option, + pub is_user_shell_command: bool, } impl ProvidesSandboxRetryData for ShellRequest { @@ -121,6 +122,9 @@ impl Approvable for ShellRuntime { policy: AskForApproval, sandbox_policy: &SandboxPolicy, ) -> bool { + if req.is_user_shell_command { + return false; + } if is_known_safe_command(&req.command) { return false; } @@ -146,7 +150,7 @@ impl Approvable for ShellRuntime { } fn wants_escalated_first_attempt(&self, req: &ShellRequest) -> bool { - req.with_escalated_permissions.unwrap_or(false) + req.is_user_shell_command || req.with_escalated_permissions.unwrap_or(false) } }