mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
Add an --approve-for-me CLI flag (#36373)
## What changed - Add `--approve-for-me` to interactive and exec commands to route approval requests through automatic review. - Configure the mode with `approval_policy="on-request"` and the `workspace-write` sandbox. - Propagate the option across root, `exec`, `resume`, and `fork` argument handling while preserving later subcommand permission overrides. ## Testing - Cover parsing, permission conflicts, root/subcommand precedence, resume handling, and the effective exec approval and sandbox modes. GitOrigin-RevId: ae969e8c18f925f943049fefff56255f10b25659
This commit is contained in:
@@ -990,6 +990,9 @@ async fn cli_main(
|
||||
let root_remote = remote.remote;
|
||||
let root_remote_auth_token_env = remote.remote_auth_token_env;
|
||||
let root_strict_config = interactive.strict_config;
|
||||
interactive
|
||||
.shared
|
||||
.take_auto_review_config_overrides(&mut root_config_overrides);
|
||||
reject_root_strict_config_for_subcommand(root_strict_config, &subcommand)?;
|
||||
if let Some(subcommand) = subcommand.as_ref() {
|
||||
profile_v2_for_subcommand(&interactive, subcommand)?;
|
||||
@@ -2570,13 +2573,19 @@ fn merge_interactive_cli_flags(interactive: &mut TuiCli, subcommand_cli: TuiCli)
|
||||
approval_policy,
|
||||
web_search,
|
||||
prompt,
|
||||
config_overrides,
|
||||
mut config_overrides,
|
||||
..
|
||||
} = subcommand_cli;
|
||||
let subcommand_auto_review = shared.auto_review;
|
||||
interactive
|
||||
.shared
|
||||
.apply_subcommand_overrides(shared.into_inner());
|
||||
if let Some(approval) = approval_policy {
|
||||
interactive
|
||||
.shared
|
||||
.take_auto_review_config_overrides(&mut config_overrides);
|
||||
if subcommand_auto_review {
|
||||
interactive.approval_policy = None;
|
||||
} else if let Some(approval) = approval_policy {
|
||||
interactive.approval_policy = Some(approval);
|
||||
}
|
||||
if web_search {
|
||||
@@ -2698,12 +2707,15 @@ mod tests {
|
||||
fn finalize_resume_from_args(args: &[&str]) -> TuiCli {
|
||||
let cli = MultitoolCli::try_parse_from(args).expect("parse");
|
||||
let MultitoolCli {
|
||||
interactive,
|
||||
config_overrides: root_overrides,
|
||||
mut interactive,
|
||||
config_overrides: mut root_overrides,
|
||||
subcommand,
|
||||
feature_toggles: _,
|
||||
remote: _,
|
||||
} = cli;
|
||||
interactive
|
||||
.shared
|
||||
.take_auto_review_config_overrides(&mut root_overrides);
|
||||
|
||||
let Subcommand::Resume(ResumeCommand {
|
||||
session_id,
|
||||
@@ -2732,12 +2744,15 @@ mod tests {
|
||||
fn finalize_fork_from_args(args: &[&str]) -> TuiCli {
|
||||
let cli = MultitoolCli::try_parse_from(args).expect("parse");
|
||||
let MultitoolCli {
|
||||
interactive,
|
||||
config_overrides: root_overrides,
|
||||
mut interactive,
|
||||
config_overrides: mut root_overrides,
|
||||
subcommand,
|
||||
feature_toggles: _,
|
||||
remote: _,
|
||||
} = cli;
|
||||
interactive
|
||||
.shared
|
||||
.take_auto_review_config_overrides(&mut root_overrides);
|
||||
|
||||
let Subcommand::Fork(ForkCommand {
|
||||
session_id,
|
||||
@@ -2754,6 +2769,22 @@ mod tests {
|
||||
finalize_fork_interactive(interactive, root_overrides, session_id, last, all, fork_cli)
|
||||
}
|
||||
|
||||
fn finalize_exec_from_args(args: &[&str]) -> ExecCli {
|
||||
let mut cli = MultitoolCli::try_parse_from(args).expect("parse");
|
||||
cli.interactive
|
||||
.shared
|
||||
.take_auto_review_config_overrides(&mut cli.config_overrides);
|
||||
let Some(Subcommand::Exec(mut exec)) = cli.subcommand else {
|
||||
panic!("expected exec subcommand");
|
||||
};
|
||||
exec.shared
|
||||
.inherit_exec_root_options(&cli.interactive.shared);
|
||||
prepend_config_flags(&mut exec.config_overrides, cli.config_overrides);
|
||||
exec.shared
|
||||
.take_auto_review_config_overrides(&mut exec.config_overrides);
|
||||
exec
|
||||
}
|
||||
|
||||
fn finalize_archive_from_args(args: &[&str]) -> (String, TuiCli, InteractiveRemoteOptions) {
|
||||
let cli = MultitoolCli::try_parse_from(args).expect("parse");
|
||||
let MultitoolCli {
|
||||
@@ -2921,6 +2952,157 @@ mod tests {
|
||||
assert_eq!(err.kind(), clap::error::ErrorKind::ArgumentConflict);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approve_for_me_configures_interactive_mode() {
|
||||
for flag in ["--approve-for-me", "--not-so-yolo"] {
|
||||
let mut cli = MultitoolCli::try_parse_from(["codex", flag]).expect("parse flag");
|
||||
|
||||
assert!(cli.interactive.auto_review);
|
||||
cli.interactive
|
||||
.shared
|
||||
.take_auto_review_config_overrides(&mut cli.interactive.config_overrides);
|
||||
assert_eq!(
|
||||
cli.interactive.config_overrides.raw_overrides,
|
||||
vec![
|
||||
r#"approvals_reviewer="auto_review""#.to_string(),
|
||||
r#"approval_policy="on-request""#.to_string(),
|
||||
r#"sandbox_mode="workspace-write""#.to_string(),
|
||||
]
|
||||
);
|
||||
assert!(!cli.interactive.auto_review);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn not_so_yolo_alias_is_hidden_from_help() {
|
||||
for args in [&["codex", "--help"][..], &["codex", "exec", "--help"][..]] {
|
||||
let help = help_from_args(args);
|
||||
|
||||
assert!(!help.contains("--not-so-yolo"), "{help}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approve_for_me_defaults_propagate_from_root_to_exec() {
|
||||
let exec = finalize_exec_from_args(&["codex", "--approve-for-me", "exec", "summarize"]);
|
||||
|
||||
assert_eq!(
|
||||
exec.config_overrides.raw_overrides,
|
||||
vec![
|
||||
r#"approvals_reviewer="auto_review""#.to_string(),
|
||||
r#"approval_policy="on-request""#.to_string(),
|
||||
r#"sandbox_mode="workspace-write""#.to_string(),
|
||||
]
|
||||
);
|
||||
assert!(exec.sandbox_mode.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn later_exec_sandbox_partially_overrides_approve_for_me() {
|
||||
let exec = finalize_exec_from_args(&[
|
||||
"codex",
|
||||
"--approve-for-me",
|
||||
"exec",
|
||||
"--sandbox",
|
||||
"read-only",
|
||||
]);
|
||||
|
||||
assert_matches!(
|
||||
exec.sandbox_mode,
|
||||
Some(codex_utils_cli::SandboxModeCliArg::ReadOnly)
|
||||
);
|
||||
assert_eq!(
|
||||
exec.config_overrides.raw_overrides,
|
||||
vec![
|
||||
r#"approvals_reviewer="auto_review""#.to_string(),
|
||||
r#"approval_policy="on-request""#.to_string(),
|
||||
r#"sandbox_mode="workspace-write""#.to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn later_approve_for_me_overrides_root_exec_sandbox() {
|
||||
let exec = finalize_exec_from_args(&[
|
||||
"codex",
|
||||
"--sandbox",
|
||||
"read-only",
|
||||
"exec",
|
||||
"--approve-for-me",
|
||||
]);
|
||||
|
||||
assert!(exec.sandbox_mode.is_none());
|
||||
assert_eq!(
|
||||
exec.config_overrides.raw_overrides,
|
||||
vec![
|
||||
r#"approvals_reviewer="auto_review""#.to_string(),
|
||||
r#"approval_policy="on-request""#.to_string(),
|
||||
r#"sandbox_mode="workspace-write""#.to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn later_resume_approval_policy_partially_overrides_approve_for_me() {
|
||||
let interactive = finalize_resume_from_args(&[
|
||||
"codex",
|
||||
"--approve-for-me",
|
||||
"resume",
|
||||
"--ask-for-approval",
|
||||
"never",
|
||||
]);
|
||||
|
||||
assert_matches!(
|
||||
interactive.approval_policy,
|
||||
Some(codex_utils_cli::ApprovalModeCliArg::Never)
|
||||
);
|
||||
assert_eq!(
|
||||
interactive.config_overrides.raw_overrides,
|
||||
vec![
|
||||
r#"approvals_reviewer="auto_review""#.to_string(),
|
||||
r#"approval_policy="on-request""#.to_string(),
|
||||
r#"sandbox_mode="workspace-write""#.to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn later_approve_for_me_overrides_root_tui_approval_policy() {
|
||||
let interactive = finalize_resume_from_args(&[
|
||||
"codex",
|
||||
"--ask-for-approval",
|
||||
"never",
|
||||
"resume",
|
||||
"--approve-for-me",
|
||||
]);
|
||||
|
||||
assert!(interactive.approval_policy.is_none());
|
||||
assert_eq!(
|
||||
interactive.config_overrides.raw_overrides,
|
||||
vec![
|
||||
r#"approvals_reviewer="auto_review""#.to_string(),
|
||||
r#"approval_policy="on-request""#.to_string(),
|
||||
r#"sandbox_mode="workspace-write""#.to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn approve_for_me_conflicts_with_explicit_interactive_permissions() {
|
||||
for conflicting_args in [
|
||||
vec!["--sandbox", "read-only"],
|
||||
vec!["--ask-for-approval", "on-request"],
|
||||
vec!["--dangerously-bypass-approvals-and-sandbox"],
|
||||
] {
|
||||
let mut args = vec!["codex", "--approve-for-me"];
|
||||
args.extend(conflicting_args);
|
||||
|
||||
let error =
|
||||
MultitoolCli::try_parse_from(args).expect_err("permission flags should conflict");
|
||||
assert_eq!(error.kind(), clap::error::ErrorKind::ArgumentConflict);
|
||||
}
|
||||
}
|
||||
|
||||
fn app_server_from_args(args: &[&str]) -> AppServerCommand {
|
||||
let cli = MultitoolCli::try_parse_from(args).expect("parse");
|
||||
let Subcommand::AppServer(app_server) = cli.subcommand.expect("app-server present") else {
|
||||
|
||||
Reference in New Issue
Block a user