Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion codex-rs/acp/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 ┌─────────────────────────┐
Expand Down
16 changes: 16 additions & 0 deletions codex-rs/acp/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ impl AcpBackend {
Arc::clone(&pending_approvals),
Arc::clone(&user_notifier),
cwd.clone(),
config.approval_policy,
));

Ok(backend)
Expand Down Expand Up @@ -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<ApprovalRequest>,
event_tx: mpsc::Sender<Event>,
pending_approvals: Arc<Mutex<Vec<ApprovalRequest>>>,
user_notifier: Arc<codex_core::UserNotifier>,
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.
Expand Down
5 changes: 4 additions & 1 deletion codex-rs/cli/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
28 changes: 22 additions & 6 deletions codex-rs/cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"
);
}

Expand Down
3 changes: 2 additions & 1 deletion codex-rs/core/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):**

Expand Down
10 changes: 9 additions & 1 deletion codex-rs/tui/docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
60 changes: 54 additions & 6 deletions codex-rs/tui/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Expand Down Expand Up @@ -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"
);
}
}
9 changes: 8 additions & 1 deletion codex-rs/tui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,14 @@ pub async fn run_main(

#[cfg(not(feature = "codex-features"))]
let (sandbox_mode, approval_policy): (Option<SandboxMode>, Option<AskForApproval>) =
(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")]
Expand Down
Loading