Skip to main content

codex_cli/agent/
exec.rs

1use nils_common::process as shared_process;
2use std::io::Write;
3use std::sync::atomic::{AtomicBool, Ordering};
4
5const DEFAULT_MODEL: &str = "gpt-5.1-codex-mini";
6const DEFAULT_REASONING: &str = "medium";
7
8static WARNED_INVALID_ALLOW_DANGEROUS: AtomicBool = AtomicBool::new(false);
9
10fn env_or_default(key: &str, default: &str) -> String {
11    std::env::var(key).unwrap_or_else(|_| default.to_string())
12}
13
14fn is_true_env(key: &str, stderr: &mut impl Write) -> bool {
15    let Ok(raw) = std::env::var(key) else {
16        return false;
17    };
18
19    let trimmed = raw.trim();
20    if trimmed.is_empty() {
21        return false;
22    }
23
24    match trimmed.to_ascii_lowercase().as_str() {
25        "true" => true,
26        "false" => false,
27        _ => {
28            if !WARNED_INVALID_ALLOW_DANGEROUS.swap(true, Ordering::SeqCst) {
29                let _ = writeln!(
30                    stderr,
31                    "warning: {key} must be true|false (got: {raw}); treating as false"
32                );
33            }
34            false
35        }
36    }
37}
38
39pub fn require_allow_dangerous(caller: Option<&str>, stderr: &mut impl Write) -> bool {
40    if is_true_env("CODEX_ALLOW_DANGEROUS_ENABLED", stderr) {
41        return true;
42    }
43
44    let prefix = match caller {
45        Some(value) if !value.is_empty() => value,
46        _ => "codex",
47    };
48    let _ = writeln!(
49        stderr,
50        "{prefix}: disabled (set CODEX_ALLOW_DANGEROUS_ENABLED=true)"
51    );
52    false
53}
54
55pub fn exec_dangerous(prompt: &str, caller: &str, stderr: &mut impl Write) -> i32 {
56    if prompt.is_empty() {
57        let _ = writeln!(stderr, "_codex_exec_dangerous: missing prompt");
58        return 1;
59    }
60
61    if !require_allow_dangerous(Some(caller), stderr) {
62        return 1;
63    }
64
65    let model = env_or_default("CODEX_CLI_MODEL", DEFAULT_MODEL);
66    let reasoning = env_or_default("CODEX_CLI_REASONING", DEFAULT_REASONING);
67    let reasoning_arg = format!("model_reasoning_effort=\"{}\"", reasoning);
68    let args = [
69        "exec",
70        "--dangerously-bypass-approvals-and-sandbox",
71        "-s",
72        "workspace-write",
73        "-m",
74        model.as_str(),
75        "-c",
76        reasoning_arg.as_str(),
77        "--",
78        prompt,
79    ];
80
81    match shared_process::run_status_inherit("codex", &args) {
82        Ok(status) => status.code().unwrap_or(1),
83        Err(err) => {
84            let _ = writeln!(stderr, "codex-tools: failed to run codex exec: {err}");
85            1
86        }
87    }
88}