diff --git a/codex-rs/acp/docs.md b/codex-rs/acp/docs.md index ca988aaa..e1ff7f7a 100644 --- a/codex-rs/acp/docs.md +++ b/codex-rs/acp/docs.md @@ -377,7 +377,21 @@ Session discovery logic (finding files in ~/.codex, ~/.gemini, ~/.claude) is def **Approval Bridging:** -The ACP module bridges permission requests to Codex's approval UI. Approval requests are handled **immediately** (not deferred) to avoid deadlocks: +The ACP module bridges permission requests to Codex's approval UI via `run_approval_handler()`. The handler respects the configured `approval_policy` from `AcpBackendConfig`: + +| Policy | Behavior | +|--------|----------| +| `AskForApproval::Never` | Auto-approve all requests immediately (yolo mode) | +| `AskForApproval::OnFailure` | Prompt only when operations fail | +| `AskForApproval::UnlessAllowListed` | Prompt except for allowed operations | + +When `approval_policy == AskForApproval::Never` (set via `--yolo` or `--dangerously-bypass-approvals-and-sandbox` CLI flags), the approval handler sends `ReviewDecision::Approved` without forwarding requests to the TUI. This completes the data flow: + +``` +CLI --yolo flag → AskForApproval::Never → AcpBackendConfig → run_approval_handler() → auto-approve +``` + +For all other policies, approval requests are handled **immediately** (not deferred) to avoid deadlocks: ``` ┌─────────────────────────┐ ApprovalRequest ┌─────────────────────────┐ diff --git a/codex-rs/acp/src/backend.rs b/codex-rs/acp/src/backend.rs index d371bfd3..74d12ade 100644 --- a/codex-rs/acp/src/backend.rs +++ b/codex-rs/acp/src/backend.rs @@ -308,6 +308,7 @@ impl AcpBackend { Arc::clone(&pending_approvals), Arc::clone(&user_notifier), cwd.clone(), + config.approval_policy, )); Ok(backend) @@ -719,14 +720,29 @@ impl AcpBackend { } /// Background task to handle approval requests from the ACP connection. + /// + /// When `approval_policy` is `AskForApproval::Never` (yolo mode), requests + /// are auto-approved without prompting the user. async fn run_approval_handler( mut approval_rx: mpsc::Receiver, event_tx: mpsc::Sender, pending_approvals: Arc>>, user_notifier: Arc, cwd: PathBuf, + approval_policy: AskForApproval, ) { while let Some(request) = approval_rx.recv().await { + // If approval_policy is Never (yolo mode), auto-approve immediately + if approval_policy == AskForApproval::Never { + debug!( + target: "acp_event_flow", + call_id = %request.event.call_id(), + "Auto-approving request (approval_policy=Never)" + ); + let _ = request.response_tx.send(ReviewDecision::Approved); + continue; + } + // Send the appropriate approval request event to TUI based on operation type. // Use the call_id as the event wrapper ID so that the TUI can // correctly route the user's decision back to this pending request. diff --git a/codex-rs/cli/docs.md b/codex-rs/cli/docs.md index 0e90f9f5..70d32947 100644 --- a/codex-rs/cli/docs.md +++ b/codex-rs/cli/docs.md @@ -68,13 +68,16 @@ The following CLI flags are gated behind the `codex-features` Cargo feature and | `--sandbox` | `-s` | Select sandbox policy | | `--ask-for-approval` | `-a` | Configure approval policy | | `--full-auto` | | Sandboxed automatic execution mode | -| `--dangerously-bypass-approvals-and-sandbox` | | Skip all safety checks (dangerous) | | `--search` | | Enable web search tool | | `--enable` | | Enable a feature toggle | | `--disable` | | Disable a feature toggle | Without `codex-features`, passing these flags results in a clap parse error. +**Always-Available Safety Override:** + +The `--dangerously-bypass-approvals-and-sandbox` flag (alias: `--yolo`) is available in all builds regardless of `codex-features`. When set, it configures `approval_policy: AskForApproval::Never`, causing the ACP backend to auto-approve all tool operations without prompting the user. + **Resume Logic (requires `codex-features`):** `nori resume` supports three modes: diff --git a/codex-rs/cli/src/main.rs b/codex-rs/cli/src/main.rs index 66d05db5..68827f11 100644 --- a/codex-rs/cli/src/main.rs +++ b/codex-rs/cli/src/main.rs @@ -1070,15 +1070,31 @@ mod tests { ); } - /// When codex-features is disabled, --dangerously-bypass-approvals-and-sandbox flag should not be recognized + /// The --dangerously-bypass-approvals-and-sandbox (--yolo) flag should be recognized + /// in all builds, regardless of whether codex-features is enabled #[test] #[cfg(not(feature = "codex-features"))] - fn dangerous_bypass_flag_rejected_without_codex_features() { - let result = - MultitoolCli::try_parse_from(["nori", "--dangerously-bypass-approvals-and-sandbox"]); + fn dangerous_bypass_flag_accepted_without_codex_features() { + let cli = + MultitoolCli::try_parse_from(["nori", "--dangerously-bypass-approvals-and-sandbox"]) + .expect( + "--dangerously-bypass-approvals-and-sandbox should be accepted in all builds", + ); assert!( - result.is_err(), - "--dangerously-bypass-approvals-and-sandbox should be rejected when codex-features is disabled" + cli.interactive.dangerously_bypass_approvals_and_sandbox, + "--dangerously-bypass-approvals-and-sandbox should set the field to true" + ); + } + + /// The --yolo alias should be recognized in all builds + #[test] + #[cfg(not(feature = "codex-features"))] + fn yolo_alias_accepted_without_codex_features() { + let cli = MultitoolCli::try_parse_from(["nori", "--yolo"]) + .expect("--yolo should be accepted in all builds"); + assert!( + cli.interactive.dangerously_bypass_approvals_and_sandbox, + "--yolo should set dangerously_bypass_approvals_and_sandbox to true" ); } diff --git a/codex-rs/core/docs.md b/codex-rs/core/docs.md index 365e5a53..1443988d 100644 --- a/codex-rs/core/docs.md +++ b/codex-rs/core/docs.md @@ -139,7 +139,8 @@ The `UserNotifier` supports two delivery modes: └─────────────────────┘ └─────────────────────┘ ``` -The `use_native` flag controls whether native notifications are sent when no external command is configured. Production code passes `true`, test code passes `false` to avoid notification spam during tests. + +Notification dispatch priority: external command takes precedence over native. If `notify_command` is `Some` (even if empty), native notifications are skipped. An empty `notify_command` (`Some(vec![])`) disables all notifications - the test infrastructure uses this by default via `load_default_config_for_test()`. **Window Focus (X11 Linux):** diff --git a/codex-rs/tui/docs.md b/codex-rs/tui/docs.md index 34536e96..bc90b7dc 100644 --- a/codex-rs/tui/docs.md +++ b/codex-rs/tui/docs.md @@ -586,9 +586,17 @@ For E2E testing, `NORI_SYNC_SYSTEM_INFO=1` env var enables synchronous collectio **Configuration Flow:** TUI respects config overrides from: -1. CLI flags (`--model` always available; `--sandbox`, `--oss`, `-a`, `--full-auto`, etc. require `codex-features`) +1. CLI flags (`--model` and `--yolo` always available; `--sandbox`, `--oss`, `-a`, `--full-auto`, etc. require `codex-features`) 2. `-c key=value` overrides 3. Config profiles (`-p profile-name`) 4. `~/.nori/cli/config.toml` +**YOLO Mode:** + +The `--yolo` flag (alias: `--dangerously-bypass-approvals-and-sandbox`) bypasses all safety mechanisms: +- Sets `SandboxMode::DangerFullAccess` (no filesystem restrictions) +- Sets `AskForApproval::Never` (no confirmation prompts for commands or file writes) + +This flag works in all builds (with or without `codex-features`). In `run_main()`, when enabled, the flag overrides any configured sandbox or approval policies before passing to `ConfigOverrides`. + Created and maintained by Nori. diff --git a/codex-rs/tui/src/cli.rs b/codex-rs/tui/src/cli.rs index ff880a14..97907002 100644 --- a/codex-rs/tui/src/cli.rs +++ b/codex-rs/tui/src/cli.rs @@ -71,12 +71,22 @@ pub struct Cli { /// Skip all confirmation prompts and execute commands without sandboxing. /// EXTREMELY DANGEROUS. Intended solely for running in environments that are externally sandboxed. - #[cfg(feature = "codex-features")] - #[arg( - long = "dangerously-bypass-approvals-and-sandbox", - alias = "yolo", - default_value_t = false, - conflicts_with_all = ["approval_policy", "full_auto"] + #[cfg_attr( + feature = "codex-features", + arg( + long = "dangerously-bypass-approvals-and-sandbox", + alias = "yolo", + default_value_t = false, + conflicts_with_all = ["approval_policy", "full_auto"] + ) + )] + #[cfg_attr( + not(feature = "codex-features"), + arg( + long = "dangerously-bypass-approvals-and-sandbox", + alias = "yolo", + default_value_t = false + ) )] pub dangerously_bypass_approvals_and_sandbox: bool, @@ -107,3 +117,41 @@ pub struct Cli { #[arg(long = "skip-trust-directory", default_value_t = false)] pub skip_trust_directory: bool, } + +#[cfg(test)] +mod tests { + use super::*; + use clap::Parser; + + /// Test that --yolo flag is recognized and sets dangerously_bypass_approvals_and_sandbox to true. + /// This flag should work without the codex-features feature flag. + #[test] + fn test_yolo_flag_is_recognized() { + let cli = Cli::try_parse_from(["nori", "--yolo"]).expect("--yolo should be a valid flag"); + assert!( + cli.dangerously_bypass_approvals_and_sandbox, + "--yolo should set dangerously_bypass_approvals_and_sandbox to true" + ); + } + + /// Test that --dangerously-bypass-approvals-and-sandbox works as the full flag name. + #[test] + fn test_dangerously_bypass_flag_is_recognized() { + let cli = Cli::try_parse_from(["nori", "--dangerously-bypass-approvals-and-sandbox"]) + .expect("--dangerously-bypass-approvals-and-sandbox should be a valid flag"); + assert!( + cli.dangerously_bypass_approvals_and_sandbox, + "--dangerously-bypass-approvals-and-sandbox should set the field to true" + ); + } + + /// Test that without --yolo, the field defaults to false. + #[test] + fn test_yolo_flag_defaults_to_false() { + let cli = Cli::try_parse_from(["nori"]).expect("basic parsing should work"); + assert!( + !cli.dangerously_bypass_approvals_and_sandbox, + "dangerously_bypass_approvals_and_sandbox should default to false" + ); + } +} diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index f3c02ce3..3ce02560 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -201,7 +201,14 @@ pub async fn run_main( #[cfg(not(feature = "codex-features"))] let (sandbox_mode, approval_policy): (Option, Option) = - (None, None); + if cli.dangerously_bypass_approvals_and_sandbox { + ( + Some(SandboxMode::DangerFullAccess), + Some(AskForApproval::Never), + ) + } else { + (None, None) + }; // Map the legacy --search flag to the new feature toggle. #[cfg(feature = "codex-features")]