From 30bea97e0fec9f13084c6dc9fe89678f561935ed Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 5 Apr 2026 23:04:46 -0700 Subject: [PATCH 1/3] Add ask-for-approval to codex exec --- codex-rs/exec/src/cli.rs | 7 +++++- codex-rs/exec/src/lib.rs | 40 +++++++++++++++++++++++++++------- codex-rs/exec/src/lib_tests.rs | 39 +++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/codex-rs/exec/src/cli.rs b/codex-rs/exec/src/cli.rs index 22c53da1ae8f..b426c91e8e95 100644 --- a/codex-rs/exec/src/cli.rs +++ b/codex-rs/exec/src/cli.rs @@ -2,6 +2,7 @@ use clap::Args; use clap::FromArgMatches; use clap::Parser; use clap::ValueEnum; +use codex_utils_cli::ApprovalModeCliArg; use codex_utils_cli::CliConfigOverrides; use std::path::PathBuf; @@ -43,6 +44,10 @@ pub struct Cli { #[arg(long = "sandbox", short = 's', value_enum)] pub sandbox_mode: Option, + /// Configure when the model requires human approval before executing a command. + #[arg(long = "ask-for-approval", short = 'a', global = true)] + pub approval_policy: Option, + /// Configuration profile from config.toml to specify default options. #[arg(long = "profile", short = 'p')] pub config_profile: Option, @@ -58,7 +63,7 @@ pub struct Cli { alias = "yolo", default_value_t = false, global = true, - conflicts_with = "full_auto" + conflicts_with_all = ["approval_policy", "full_auto"] )] pub dangerously_bypass_approvals_and_sandbox: bool, diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index b7779e0c073a..8711712913da 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -174,6 +174,30 @@ fn exec_root_span() -> tracing::Span { ) } +fn resolve_execution_policies( + approval_policy_cli_arg: Option, + sandbox_mode_cli_arg: Option, + full_auto: bool, + dangerously_bypass_approvals_and_sandbox: bool, +) -> (Option, Option) { + if full_auto { + ( + Some(AskForApproval::OnRequest), + Some(SandboxMode::WorkspaceWrite), + ) + } else if dangerously_bypass_approvals_and_sandbox { + ( + Some(AskForApproval::Never), + Some(SandboxMode::DangerFullAccess), + ) + } else { + ( + approval_policy_cli_arg.map(Into::into), + sandbox_mode_cli_arg.map(Into::into), + ) + } +} + pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> { if let Err(err) = set_default_originator("codex_exec".to_string()) { tracing::warn!(?err, "Failed to set codex exec originator override {err:?}"); @@ -196,6 +220,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result last_message_file, json: json_mode, sandbox_mode: sandbox_mode_cli_arg, + approval_policy: approval_policy_cli_arg, prompt, output_schema: output_schema_path, config_overrides, @@ -222,13 +247,12 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result .with_writer(std::io::stderr) .with_filter(env_filter); - let sandbox_mode = if full_auto { - Some(SandboxMode::WorkspaceWrite) - } else if dangerously_bypass_approvals_and_sandbox { - Some(SandboxMode::DangerFullAccess) - } else { - sandbox_mode_cli_arg.map(Into::::into) - }; + let (approval_policy, sandbox_mode) = resolve_execution_policies( + approval_policy_cli_arg, + sandbox_mode_cli_arg, + full_auto, + dangerously_bypass_approvals_and_sandbox, + ); // Parse `-c` overrides from the CLI. let cli_kv_overrides = match config_overrides.parse_overrides() { @@ -333,7 +357,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result review_model: None, config_profile, // Default to never ask for approvals in headless mode. Feature flags can override. - approval_policy: Some(AskForApproval::Never), + approval_policy: approval_policy.or(Some(AskForApproval::Never)), approvals_reviewer: None, sandbox_mode, cwd: resolved_cwd, diff --git a/codex-rs/exec/src/lib_tests.rs b/codex-rs/exec/src/lib_tests.rs index 09b43471dfa1..8dadd73a19d7 100644 --- a/codex-rs/exec/src/lib_tests.rs +++ b/codex-rs/exec/src/lib_tests.rs @@ -1,4 +1,5 @@ use super::*; +use clap::Parser; use codex_otel::set_parent_from_w3c_trace_context; use codex_protocol::config_types::ApprovalsReviewer; use opentelemetry::trace::TraceContextExt; @@ -20,6 +21,44 @@ fn exec_defaults_analytics_to_enabled() { assert_eq!(DEFAULT_ANALYTICS_ENABLED, true); } +#[test] +fn exec_cli_accepts_ask_for_approval_flag() { + let cli = Cli::try_parse_from(["codex-exec", "--ask-for-approval", "never", "test"]) + .expect("parse exec cli"); + + assert!(matches!( + cli.approval_policy, + Some(codex_utils_cli::ApprovalModeCliArg::Never) + )); + assert_eq!(cli.prompt, Some("test".to_string())); +} + +#[test] +fn resolve_execution_policies_uses_explicit_approval_policy() { + let (approval_policy, sandbox_mode) = resolve_execution_policies( + Some(codex_utils_cli::ApprovalModeCliArg::Never), + Some(codex_utils_cli::SandboxModeCliArg::ReadOnly), + /*full_auto*/ false, + /*dangerously_bypass_approvals_and_sandbox*/ false, + ); + + assert_eq!(approval_policy, Some(AskForApproval::Never)); + assert_eq!(sandbox_mode, Some(SandboxMode::ReadOnly)); +} + +#[test] +fn resolve_execution_policies_prioritizes_full_auto() { + let (approval_policy, sandbox_mode) = resolve_execution_policies( + Some(codex_utils_cli::ApprovalModeCliArg::Never), + Some(codex_utils_cli::SandboxModeCliArg::ReadOnly), + /*full_auto*/ true, + /*dangerously_bypass_approvals_and_sandbox*/ false, + ); + + assert_eq!(approval_policy, Some(AskForApproval::OnRequest)); + assert_eq!(sandbox_mode, Some(SandboxMode::WorkspaceWrite)); +} + #[test] fn exec_root_span_can_be_parented_from_trace_context() { let subscriber = test_tracing_subscriber(); From 3d85902a1096e6c26c301689ae2ceffb0433febe Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 5 Apr 2026 23:11:12 -0700 Subject: [PATCH 2/3] Clarify codex exec approval help --- codex-rs/exec/src/cli.rs | 9 ++----- codex-rs/exec/src/lib.rs | 40 ++++++---------------------- codex-rs/exec/src/lib_tests.rs | 48 ++++++++++++---------------------- 3 files changed, 27 insertions(+), 70 deletions(-) diff --git a/codex-rs/exec/src/cli.rs b/codex-rs/exec/src/cli.rs index b426c91e8e95..0225dc1768d4 100644 --- a/codex-rs/exec/src/cli.rs +++ b/codex-rs/exec/src/cli.rs @@ -2,7 +2,6 @@ use clap::Args; use clap::FromArgMatches; use clap::Parser; use clap::ValueEnum; -use codex_utils_cli::ApprovalModeCliArg; use codex_utils_cli::CliConfigOverrides; use std::path::PathBuf; @@ -44,15 +43,11 @@ pub struct Cli { #[arg(long = "sandbox", short = 's', value_enum)] pub sandbox_mode: Option, - /// Configure when the model requires human approval before executing a command. - #[arg(long = "ask-for-approval", short = 'a', global = true)] - pub approval_policy: Option, - /// Configuration profile from config.toml to specify default options. #[arg(long = "profile", short = 'p')] pub config_profile: Option, - /// Convenience alias for low-friction sandboxed automatic execution (-a on-request, --sandbox workspace-write). + /// Convenience alias for low-friction sandboxed automatic execution (--sandbox workspace-write). #[arg(long = "full-auto", default_value_t = false, global = true)] pub full_auto: bool, @@ -63,7 +58,7 @@ pub struct Cli { alias = "yolo", default_value_t = false, global = true, - conflicts_with_all = ["approval_policy", "full_auto"] + conflicts_with = "full_auto" )] pub dangerously_bypass_approvals_and_sandbox: bool, diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs index 8711712913da..b7779e0c073a 100644 --- a/codex-rs/exec/src/lib.rs +++ b/codex-rs/exec/src/lib.rs @@ -174,30 +174,6 @@ fn exec_root_span() -> tracing::Span { ) } -fn resolve_execution_policies( - approval_policy_cli_arg: Option, - sandbox_mode_cli_arg: Option, - full_auto: bool, - dangerously_bypass_approvals_and_sandbox: bool, -) -> (Option, Option) { - if full_auto { - ( - Some(AskForApproval::OnRequest), - Some(SandboxMode::WorkspaceWrite), - ) - } else if dangerously_bypass_approvals_and_sandbox { - ( - Some(AskForApproval::Never), - Some(SandboxMode::DangerFullAccess), - ) - } else { - ( - approval_policy_cli_arg.map(Into::into), - sandbox_mode_cli_arg.map(Into::into), - ) - } -} - pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> { if let Err(err) = set_default_originator("codex_exec".to_string()) { tracing::warn!(?err, "Failed to set codex exec originator override {err:?}"); @@ -220,7 +196,6 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result last_message_file, json: json_mode, sandbox_mode: sandbox_mode_cli_arg, - approval_policy: approval_policy_cli_arg, prompt, output_schema: output_schema_path, config_overrides, @@ -247,12 +222,13 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result .with_writer(std::io::stderr) .with_filter(env_filter); - let (approval_policy, sandbox_mode) = resolve_execution_policies( - approval_policy_cli_arg, - sandbox_mode_cli_arg, - full_auto, - dangerously_bypass_approvals_and_sandbox, - ); + let sandbox_mode = if full_auto { + Some(SandboxMode::WorkspaceWrite) + } else if dangerously_bypass_approvals_and_sandbox { + Some(SandboxMode::DangerFullAccess) + } else { + sandbox_mode_cli_arg.map(Into::::into) + }; // Parse `-c` overrides from the CLI. let cli_kv_overrides = match config_overrides.parse_overrides() { @@ -357,7 +333,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result review_model: None, config_profile, // Default to never ask for approvals in headless mode. Feature flags can override. - approval_policy: approval_policy.or(Some(AskForApproval::Never)), + approval_policy: Some(AskForApproval::Never), approvals_reviewer: None, sandbox_mode, cwd: resolved_cwd, diff --git a/codex-rs/exec/src/lib_tests.rs b/codex-rs/exec/src/lib_tests.rs index 8dadd73a19d7..c54bcb40c583 100644 --- a/codex-rs/exec/src/lib_tests.rs +++ b/codex-rs/exec/src/lib_tests.rs @@ -1,4 +1,5 @@ use super::*; +use clap::CommandFactory; use clap::Parser; use codex_otel::set_parent_from_w3c_trace_context; use codex_protocol::config_types::ApprovalsReviewer; @@ -22,41 +23,26 @@ fn exec_defaults_analytics_to_enabled() { } #[test] -fn exec_cli_accepts_ask_for_approval_flag() { - let cli = Cli::try_parse_from(["codex-exec", "--ask-for-approval", "never", "test"]) - .expect("parse exec cli"); - - assert!(matches!( - cli.approval_policy, - Some(codex_utils_cli::ApprovalModeCliArg::Never) - )); - assert_eq!(cli.prompt, Some("test".to_string())); -} - -#[test] -fn resolve_execution_policies_uses_explicit_approval_policy() { - let (approval_policy, sandbox_mode) = resolve_execution_policies( - Some(codex_utils_cli::ApprovalModeCliArg::Never), - Some(codex_utils_cli::SandboxModeCliArg::ReadOnly), - /*full_auto*/ false, - /*dangerously_bypass_approvals_and_sandbox*/ false, - ); +fn exec_cli_rejects_ask_for_approval_flag() { + let err = Cli::try_parse_from(["codex-exec", "--ask-for-approval", "never", "test"]) + .expect_err("exec should reject ask-for-approval"); - assert_eq!(approval_policy, Some(AskForApproval::Never)); - assert_eq!(sandbox_mode, Some(SandboxMode::ReadOnly)); + assert_eq!(err.kind(), clap::error::ErrorKind::UnknownArgument); + let rendered = err.to_string(); + assert!(rendered.contains("--ask-for-approval")); } #[test] -fn resolve_execution_policies_prioritizes_full_auto() { - let (approval_policy, sandbox_mode) = resolve_execution_policies( - Some(codex_utils_cli::ApprovalModeCliArg::Never), - Some(codex_utils_cli::SandboxModeCliArg::ReadOnly), - /*full_auto*/ true, - /*dangerously_bypass_approvals_and_sandbox*/ false, - ); - - assert_eq!(approval_policy, Some(AskForApproval::OnRequest)); - assert_eq!(sandbox_mode, Some(SandboxMode::WorkspaceWrite)); +fn exec_help_does_not_reference_approval_policy_flag() { + let mut cmd = Cli::command(); + let mut help = Vec::new(); + cmd.write_long_help(&mut help).expect("render exec help"); + let help = String::from_utf8(help).expect("exec help should be utf-8"); + + assert!(help.contains("--full-auto")); + assert!(help.contains("workspace-write")); + assert!(!help.contains("-a on-request")); + assert!(!help.contains("--ask-for-approval")); } #[test] From d62b90b1bb6aad3b787ad8ed4a3b3e8005cf59e6 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Sun, 5 Apr 2026 23:13:41 -0700 Subject: [PATCH 3/3] Trim exec help regression tests --- codex-rs/exec/src/lib_tests.rs | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/codex-rs/exec/src/lib_tests.rs b/codex-rs/exec/src/lib_tests.rs index c54bcb40c583..09b43471dfa1 100644 --- a/codex-rs/exec/src/lib_tests.rs +++ b/codex-rs/exec/src/lib_tests.rs @@ -1,6 +1,4 @@ use super::*; -use clap::CommandFactory; -use clap::Parser; use codex_otel::set_parent_from_w3c_trace_context; use codex_protocol::config_types::ApprovalsReviewer; use opentelemetry::trace::TraceContextExt; @@ -22,29 +20,6 @@ fn exec_defaults_analytics_to_enabled() { assert_eq!(DEFAULT_ANALYTICS_ENABLED, true); } -#[test] -fn exec_cli_rejects_ask_for_approval_flag() { - let err = Cli::try_parse_from(["codex-exec", "--ask-for-approval", "never", "test"]) - .expect_err("exec should reject ask-for-approval"); - - assert_eq!(err.kind(), clap::error::ErrorKind::UnknownArgument); - let rendered = err.to_string(); - assert!(rendered.contains("--ask-for-approval")); -} - -#[test] -fn exec_help_does_not_reference_approval_policy_flag() { - let mut cmd = Cli::command(); - let mut help = Vec::new(); - cmd.write_long_help(&mut help).expect("render exec help"); - let help = String::from_utf8(help).expect("exec help should be utf-8"); - - assert!(help.contains("--full-auto")); - assert!(help.contains("workspace-write")); - assert!(!help.contains("-a on-request")); - assert!(!help.contains("--ask-for-approval")); -} - #[test] fn exec_root_span_can_be_parented_from_trace_context() { let subscriber = test_tracing_subscriber();