Fix exec inheritance of root shared flags (#18630)

Addresses #18113

Problem: Shared flags provided before the exec subcommand were parsed by
the root CLI but not inherited by the exec CLI, so exec sessions could
run with stale or default sandbox and model configuration.

Solution: Move shared TUI and exec flags into a common option block and
merge root selections into exec before dispatch, while preserving exec's
global subcommand flag behavior.
This commit is contained in:
Eric Traut
2026-04-20 16:12:17 -07:00
committed by GitHub
parent 2af4f15479
commit 0f1c9b8963
7 changed files with 372 additions and 158 deletions

View File

@@ -2,8 +2,10 @@ mod approval_mode_cli_arg;
mod config_override;
pub(crate) mod format_env_display;
mod sandbox_mode_cli_arg;
mod shared_options;
pub use approval_mode_cli_arg::ApprovalModeCliArg;
pub use config_override::CliConfigOverrides;
pub use format_env_display::format_env_display;
pub use sandbox_mode_cli_arg::SandboxModeCliArg;
pub use shared_options::SharedCliOptions;

View File

@@ -0,0 +1,174 @@
//! Shared command-line flags used by both interactive and non-interactive Codex entry points.
use crate::SandboxModeCliArg;
use clap::Args;
use std::path::PathBuf;
#[derive(Args, Debug, Default)]
pub struct SharedCliOptions {
/// Optional image(s) to attach to the initial prompt.
#[arg(
long = "image",
short = 'i',
value_name = "FILE",
value_delimiter = ',',
num_args = 1..
)]
pub images: Vec<PathBuf>,
/// Model the agent should use.
#[arg(long, short = 'm')]
pub model: Option<String>,
/// Use open-source provider.
#[arg(long = "oss", default_value_t = false)]
pub oss: bool,
/// Specify which local provider to use (lmstudio or ollama).
/// If not specified with --oss, will use config default or show selection.
#[arg(long = "local-provider")]
pub oss_provider: Option<String>,
/// Configuration profile from config.toml to specify default options.
#[arg(long = "profile", short = 'p')]
pub config_profile: Option<String>,
/// Select the sandbox policy to use when executing model-generated shell
/// commands.
#[arg(long = "sandbox", short = 's')]
pub sandbox_mode: Option<SandboxModeCliArg>,
/// Convenience alias for low-friction sandboxed automatic execution.
#[arg(long = "full-auto", default_value_t = false)]
pub full_auto: bool,
/// Skip all confirmation prompts and execute commands without sandboxing.
/// EXTREMELY DANGEROUS. Intended solely for running in environments that are externally sandboxed.
#[arg(
long = "dangerously-bypass-approvals-and-sandbox",
alias = "yolo",
default_value_t = false,
conflicts_with = "full_auto"
)]
pub dangerously_bypass_approvals_and_sandbox: bool,
/// Tell the agent to use the specified directory as its working root.
#[clap(long = "cd", short = 'C', value_name = "DIR")]
pub cwd: Option<PathBuf>,
/// Additional directories that should be writable alongside the primary workspace.
#[arg(long = "add-dir", value_name = "DIR", value_hint = clap::ValueHint::DirPath)]
pub add_dir: Vec<PathBuf>,
}
impl SharedCliOptions {
pub fn inherit_exec_root_options(&mut self, root: &Self) {
let self_selected_sandbox_mode = self.sandbox_mode.is_some()
|| self.full_auto
|| self.dangerously_bypass_approvals_and_sandbox;
let Self {
images,
model,
oss,
oss_provider,
config_profile,
sandbox_mode,
full_auto,
dangerously_bypass_approvals_and_sandbox,
cwd,
add_dir,
} = self;
let Self {
images: root_images,
model: root_model,
oss: root_oss,
oss_provider: root_oss_provider,
config_profile: root_config_profile,
sandbox_mode: root_sandbox_mode,
full_auto: root_full_auto,
dangerously_bypass_approvals_and_sandbox: root_dangerously_bypass_approvals_and_sandbox,
cwd: root_cwd,
add_dir: root_add_dir,
} = root;
if model.is_none() {
model.clone_from(root_model);
}
if *root_oss {
*oss = true;
}
if oss_provider.is_none() {
oss_provider.clone_from(root_oss_provider);
}
if config_profile.is_none() {
config_profile.clone_from(root_config_profile);
}
if sandbox_mode.is_none() {
*sandbox_mode = *root_sandbox_mode;
}
if !self_selected_sandbox_mode {
*full_auto = *root_full_auto;
*dangerously_bypass_approvals_and_sandbox =
*root_dangerously_bypass_approvals_and_sandbox;
}
if cwd.is_none() {
cwd.clone_from(root_cwd);
}
if !root_images.is_empty() {
let mut merged_images = root_images.clone();
merged_images.append(images);
*images = merged_images;
}
if !root_add_dir.is_empty() {
let mut merged_add_dir = root_add_dir.clone();
merged_add_dir.append(add_dir);
*add_dir = merged_add_dir;
}
}
pub fn apply_subcommand_overrides(&mut self, subcommand: Self) {
let subcommand_selected_sandbox_mode = subcommand.sandbox_mode.is_some()
|| subcommand.full_auto
|| subcommand.dangerously_bypass_approvals_and_sandbox;
let Self {
images,
model,
oss,
oss_provider,
config_profile,
sandbox_mode,
full_auto,
dangerously_bypass_approvals_and_sandbox,
cwd,
add_dir,
} = subcommand;
if let Some(model) = model {
self.model = Some(model);
}
if oss {
self.oss = true;
}
if let Some(oss_provider) = oss_provider {
self.oss_provider = Some(oss_provider);
}
if let Some(config_profile) = config_profile {
self.config_profile = Some(config_profile);
}
if subcommand_selected_sandbox_mode {
self.sandbox_mode = sandbox_mode;
self.full_auto = full_auto;
self.dangerously_bypass_approvals_and_sandbox =
dangerously_bypass_approvals_and_sandbox;
}
if let Some(cwd) = cwd {
self.cwd = Some(cwd);
}
if !images.is_empty() {
self.images = images;
}
if !add_dir.is_empty() {
self.add_dir.extend(add_dir);
}
}
}