49 KiB
PR #1929: [config] Onboarding flow with persistence
- URL: https://github.com/openai/codex/pull/1929
- Author: dylan-hurd-oai
- Created: 2025-08-07 07:20:33 UTC
- Updated: 2025-08-07 16:27:47 UTC
- Changes: +434/-189, Files changed: 13, Commits: 20
Description
Summary
In collaboration with @gpeal: upgrade the onboarding flow, and persist user settings.
Testing
Tested a few scenarios locally
Full Diff
diff --git a/codex-rs/Cargo.lock b/codex-rs/Cargo.lock
index eabd9f35db..4eddf7bd7b 100644
--- a/codex-rs/Cargo.lock
+++ b/codex-rs/Cargo.lock
@@ -708,6 +708,7 @@ dependencies = [
"tokio-test",
"tokio-util",
"toml 0.9.4",
+ "toml_edit 0.23.3",
"tracing",
"tree-sitter",
"tree-sitter-bash",
@@ -3273,7 +3274,7 @@ version = "3.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
dependencies = [
- "toml_edit",
+ "toml_edit 0.22.27",
]
[[package]]
@@ -4800,7 +4801,7 @@ dependencies = [
"serde",
"serde_spanned 0.6.9",
"toml_datetime 0.6.11",
- "toml_edit",
+ "toml_edit 0.22.27",
]
[[package]]
@@ -4849,11 +4850,24 @@ dependencies = [
"winnow",
]
+[[package]]
+name = "toml_edit"
+version = "0.23.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17d3b47e6b7a040216ae5302712c94d1cf88c95b47efa80e2c59ce96c878267e"
+dependencies = [
+ "indexmap 2.10.0",
+ "toml_datetime 0.7.0",
+ "toml_parser",
+ "toml_writer",
+ "winnow",
+]
+
[[package]]
name = "toml_parser"
-version = "1.0.1"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97200572db069e74c512a14117b296ba0a80a30123fbbb5aa1f4a348f639ca30"
+checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10"
dependencies = [
"winnow",
]
diff --git a/codex-rs/core/Cargo.toml b/codex-rs/core/Cargo.toml
index e9d6970ded..006a218abf 100644
--- a/codex-rs/core/Cargo.toml
+++ b/codex-rs/core/Cargo.toml
@@ -36,6 +36,7 @@ sha1 = "0.10.6"
shlex = "1.3.0"
similar = "2.7.0"
strum_macros = "0.27.2"
+tempfile = "3"
thiserror = "2.0.12"
time = { version = "0.3", features = ["formatting", "local-offset", "macros"] }
tokio = { version = "1", features = [
@@ -47,6 +48,7 @@ tokio = { version = "1", features = [
] }
tokio-util = "0.7.14"
toml = "0.9.4"
+toml_edit = "0.23.3"
tracing = { version = "0.1.41", features = ["log"] }
tree-sitter = "0.25.8"
tree-sitter-bash = "0.25.0"
diff --git a/codex-rs/core/src/config.rs b/codex-rs/core/src/config.rs
index 081306dab1..723ee5f817 100644
--- a/codex-rs/core/src/config.rs
+++ b/codex-rs/core/src/config.rs
@@ -22,13 +22,17 @@ use serde::Deserialize;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
+use tempfile::NamedTempFile;
use toml::Value as TomlValue;
+use toml_edit::DocumentMut;
/// Maximum number of bytes of the documentation that will be embedded. Larger
/// files are *silently truncated* to this size so we do not take up too much of
/// the context window.
pub(crate) const PROJECT_DOC_MAX_BYTES: usize = 32 * 1024; // 32 KiB
+const CONFIG_TOML_FILE: &str = "config.toml";
+
/// Application configuration loaded from disk and merged with overrides.
#[derive(Debug, Clone, PartialEq)]
pub struct Config {
@@ -191,10 +195,28 @@ impl Config {
}
}
+pub fn load_config_as_toml_with_cli_overrides(
+ codex_home: &Path,
+ cli_overrides: Vec<(String, TomlValue)>,
+) -> std::io::Result<ConfigToml> {
+ let mut root_value = load_config_as_toml(codex_home)?;
+
+ for (path, value) in cli_overrides.into_iter() {
+ apply_toml_override(&mut root_value, &path, value);
+ }
+
+ let cfg: ConfigToml = root_value.try_into().map_err(|e| {
+ tracing::error!("Failed to deserialize overridden config: {e}");
+ std::io::Error::new(std::io::ErrorKind::InvalidData, e)
+ })?;
+
+ Ok(cfg)
+}
+
/// Read `CODEX_HOME/config.toml` and return it as a generic TOML value. Returns
/// an empty TOML table when the file does not exist.
-fn load_config_as_toml(codex_home: &Path) -> std::io::Result<TomlValue> {
- let config_path = codex_home.join("config.toml");
+pub fn load_config_as_toml(codex_home: &Path) -> std::io::Result<TomlValue> {
+ let config_path = codex_home.join(CONFIG_TOML_FILE);
match std::fs::read_to_string(&config_path) {
Ok(contents) => match toml::from_str::<TomlValue>(&contents) {
Ok(val) => Ok(val),
@@ -214,6 +236,35 @@ fn load_config_as_toml(codex_home: &Path) -> std::io::Result<TomlValue> {
}
}
+/// Patch `CODEX_HOME/config.toml` project state.
+/// Use with caution.
+pub fn set_project_trusted(codex_home: &Path, project_path: &Path) -> anyhow::Result<()> {
+ let config_path = codex_home.join(CONFIG_TOML_FILE);
+ // Parse existing config if present; otherwise start a new document.
+ let mut doc = match std::fs::read_to_string(config_path.clone()) {
+ Ok(s) => s.parse::<DocumentMut>()?,
+ Err(e) if e.kind() == std::io::ErrorKind::NotFound => DocumentMut::new(),
+ Err(e) => return Err(e.into()),
+ };
+
+ // Mark the project as trusted. toml_edit is very good at handling
+ // missing properties
+ let project_key = project_path.to_string_lossy().to_string();
+ doc["projects"][project_key.as_str()]["trust_level"] = toml_edit::value("trusted");
+
+ // ensure codex_home exists
+ std::fs::create_dir_all(codex_home)?;
+
+ // create a tmp_file
+ let tmp_file = NamedTempFile::new_in(codex_home)?;
+ std::fs::write(tmp_file.path(), doc.to_string())?;
+
+ // atomically move the tmp file into config.toml
+ tmp_file.persist(config_path)?;
+
+ Ok(())
+}
+
/// Apply a single dotted-path override onto a TOML value.
fn apply_toml_override(root: &mut TomlValue, path: &str, value: TomlValue) {
use toml::value::Table;
@@ -350,6 +401,13 @@ pub struct ConfigToml {
/// The value for the `originator` header included with Responses API requests.
pub internal_originator: Option<String>,
+
+ pub projects: Option<HashMap<String, ProjectConfig>>,
+}
+
+#[derive(Deserialize, Debug, Clone, PartialEq, Eq)]
+pub struct ProjectConfig {
+ pub trust_level: Option<String>,
}
impl ConfigToml {
@@ -377,6 +435,36 @@ impl ConfigToml {
SandboxMode::DangerFullAccess => SandboxPolicy::DangerFullAccess,
}
}
+
+ pub fn is_cwd_trusted(&self, resolved_cwd: &Path) -> bool {
+ let projects = self.projects.clone().unwrap_or_default();
+
+ projects
+ .get(&resolved_cwd.to_string_lossy().to_string())
+ .map(|p| p.trust_level.clone().unwrap_or("".to_string()) == "trusted")
+ .unwrap_or(false)
+ }
+
+ pub fn get_config_profile(
+ &self,
+ override_profile: Option<String>,
+ ) -> Result<ConfigProfile, std::io::Error> {
+ let profile = override_profile.or_else(|| self.profile.clone());
+
+ match profile {
+ Some(key) => {
+ if let Some(profile) = self.profiles.get(key.as_str()) {
+ return Ok(profile.clone());
+ }
+
+ Err(std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ format!("config profile `{key}` not found"),
+ ))
+ }
+ None => Ok(ConfigProfile::default()),
+ }
+ }
}
/// Optional overrides for user configuration (e.g., from CLI flags).
diff --git a/codex-rs/core/src/protocol.rs b/codex-rs/core/src/protocol.rs
index c789798bcd..9008ad307d 100644
--- a/codex-rs/core/src/protocol.rs
+++ b/codex-rs/core/src/protocol.rs
@@ -139,7 +139,6 @@ pub enum AskForApproval {
/// Under this policy, only "known safe" commands—as determined by
/// `is_safe_command()`—that **only read files** are auto‑approved.
/// Everything else will ask the user to approve.
- #[default]
#[serde(rename = "untrusted")]
#[strum(serialize = "untrusted")]
UnlessTrusted,
@@ -151,6 +150,7 @@ pub enum AskForApproval {
OnFailure,
/// The model decides when to ask the user for approval.
+ #[default]
OnRequest,
/// Never ask the user to approve commands. Failures are immediately returned
diff --git a/codex-rs/exec/src/lib.rs b/codex-rs/exec/src/lib.rs
index 5d7f1281ee..6ed57898b2 100644
--- a/codex-rs/exec/src/lib.rs
+++ b/codex-rs/exec/src/lib.rs
@@ -181,7 +181,7 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
event_processor.print_config_summary(&config, &prompt);
if !skip_git_repo_check && !is_inside_git_repo(&config.cwd.to_path_buf()) {
- eprintln!("Not inside a Git repo and --skip-git-repo-check was not specified.");
+ eprintln!("Not inside a trusted directory and --skip-git-repo-check was not specified.");
std::process::exit(1);
}
diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs
index 1ba8883b0b..d71a331e3b 100644
--- a/codex-rs/tui/src/app.rs
+++ b/codex-rs/tui/src/app.rs
@@ -13,7 +13,6 @@ use codex_core::config::Config;
use codex_core::protocol::Event;
use codex_core::protocol::EventMsg;
use codex_core::protocol::Op;
-use codex_core::util::is_inside_git_repo;
use color_eyre::eyre::Result;
use crossterm::SynchronizedUpdate;
use crossterm::event::KeyCode;
@@ -71,7 +70,7 @@ pub(crate) struct App<'a> {
/// deferred until after the Git warning screen is dismissed.
#[derive(Clone, Debug)]
pub(crate) struct ChatWidgetArgs {
- config: Config,
+ pub(crate) config: Config,
initial_prompt: Option<String>,
initial_images: Vec<PathBuf>,
enhanced_keys_supported: bool,
@@ -81,8 +80,8 @@ impl App<'_> {
pub(crate) fn new(
config: Config,
initial_prompt: Option<String>,
- skip_git_repo_check: bool,
initial_images: Vec<std::path::PathBuf>,
+ show_trust_screen: bool,
) -> Self {
let (app_event_tx, app_event_rx) = channel();
let app_event_tx = AppEventSender::new(app_event_tx);
@@ -134,9 +133,7 @@ impl App<'_> {
}
let show_login_screen = should_show_login_screen(&config);
- let show_git_warning =
- !skip_git_repo_check && !is_inside_git_repo(&config.cwd.to_path_buf());
- let app_state = if show_login_screen || show_git_warning {
+ let app_state = if show_login_screen || show_trust_screen {
let chat_widget_args = ChatWidgetArgs {
config: config.clone(),
initial_prompt,
@@ -149,7 +146,7 @@ impl App<'_> {
codex_home: config.codex_home.clone(),
cwd: config.cwd.clone(),
show_login_screen,
- show_git_warning,
+ show_trust_screen,
chat_widget_args,
}),
}
diff --git a/codex-rs/tui/src/cli.rs b/codex-rs/tui/src/cli.rs
index 078936dc33..91ee9cfdc7 100644
--- a/codex-rs/tui/src/cli.rs
+++ b/codex-rs/tui/src/cli.rs
@@ -54,10 +54,6 @@ pub struct Cli {
#[clap(long = "cd", short = 'C', value_name = "DIR")]
pub cwd: Option<PathBuf>,
- /// Allow running Codex outside a Git repository.
- #[arg(long = "skip-git-repo-check", default_value_t = false)]
- pub skip_git_repo_check: bool,
-
#[clap(skip)]
pub config_overrides: CliConfigOverrides,
}
diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs
index 0e809afdbe..057d25168b 100644
--- a/codex-rs/tui/src/lib.rs
+++ b/codex-rs/tui/src/lib.rs
@@ -6,8 +6,12 @@ use app::App;
use codex_core::BUILT_IN_OSS_MODEL_PROVIDER_ID;
use codex_core::config::Config;
use codex_core::config::ConfigOverrides;
+use codex_core::config::ConfigToml;
+use codex_core::config::find_codex_home;
+use codex_core::config::load_config_as_toml_with_cli_overrides;
use codex_core::config_types::SandboxMode;
use codex_core::protocol::AskForApproval;
+use codex_core::protocol::SandboxPolicy;
use codex_login::load_auth;
use codex_ollama::DEFAULT_OSS_MODEL;
use log_layer::TuiLogLayer;
@@ -89,33 +93,38 @@ pub async fn run_main(
None
};
- let config = {
+ // canonicalize the cwd
+ let cwd = cli.cwd.clone().map(|p| p.canonicalize().unwrap_or(p));
+
+ let overrides = ConfigOverrides {
+ model,
+ approval_policy,
+ sandbox_mode,
+ cwd,
+ model_provider: model_provider_override,
+ config_profile: cli.config_profile.clone(),
+ codex_linux_sandbox_exe,
+ base_instructions: None,
+ include_plan_tool: Some(true),
+ disable_response_storage: cli.oss.then_some(true),
+ show_raw_agent_reasoning: cli.oss.then_some(true),
+ };
+
+ // Parse `-c` overrides from the CLI.
+ let cli_kv_overrides = match cli.config_overrides.parse_overrides() {
+ Ok(v) => v,
+ #[allow(clippy::print_stderr)]
+ Err(e) => {
+ eprintln!("Error parsing -c overrides: {e}");
+ std::process::exit(1);
+ }
+ };
+
+ let mut config = {
// Load configuration and support CLI overrides.
- let overrides = ConfigOverrides {
- model,
- approval_policy,
- sandbox_mode,
- cwd: cli.cwd.clone().map(|p| p.canonicalize().unwrap_or(p)),
- model_provider: model_provider_override,
- config_profile: cli.config_profile.clone(),
- codex_linux_sandbox_exe,
- base_instructions: None,
- include_plan_tool: Some(true),
- disable_response_storage: cli.oss.then_some(true),
- show_raw_agent_reasoning: cli.oss.then_some(true),
- };
- // Parse `-c` overrides from the CLI.
- let cli_kv_overrides = match cli.config_overrides.parse_overrides() {
- Ok(v) => v,
- #[allow(clippy::print_stderr)]
- Err(e) => {
- eprintln!("Error parsing -c overrides: {e}");
- std::process::exit(1);
- }
- };
#[allow(clippy::print_stderr)]
- match Config::load_with_cli_overrides(cli_kv_overrides, overrides) {
+ match Config::load_with_cli_overrides(cli_kv_overrides.clone(), overrides) {
Ok(config) => config,
Err(err) => {
eprintln!("Error loading configuration: {err}");
@@ -124,6 +133,34 @@ pub async fn run_main(
}
};
+ // we load config.toml here to determine project state.
+ #[allow(clippy::print_stderr)]
+ let config_toml = {
+ let codex_home = match find_codex_home() {
+ Ok(codex_home) => codex_home,
+ Err(err) => {
+ eprintln!("Error finding codex home: {err}");
+ std::process::exit(1);
+ }
+ };
+
+ match load_config_as_toml_with_cli_overrides(&codex_home, cli_kv_overrides) {
+ Ok(config_toml) => config_toml,
+ Err(err) => {
+ eprintln!("Error loading config.toml: {err}");
+ std::process::exit(1);
+ }
+ }
+ };
+
+ let should_show_trust_screen = determine_repo_trust_state(
+ &mut config,
+ &config_toml,
+ approval_policy,
+ sandbox_mode,
+ cli.config_profile.clone(),
+ )?;
+
let log_dir = codex_core::config::log_dir(&config)?;
std::fs::create_dir_all(&log_dir)?;
// Open (or create) your log file, appending to it.
@@ -204,12 +241,14 @@ pub async fn run_main(
eprintln!("");
}
- run_ratatui_app(cli, config, log_rx).map_err(|err| std::io::Error::other(err.to_string()))
+ run_ratatui_app(cli, config, should_show_trust_screen, log_rx)
+ .map_err(|err| std::io::Error::other(err.to_string()))
}
fn run_ratatui_app(
cli: Cli,
config: Config,
+ should_show_trust_screen: bool,
mut log_rx: tokio::sync::mpsc::UnboundedReceiver<String>,
) -> color_eyre::Result<codex_core::protocol::TokenUsage> {
color_eyre::install()?;
@@ -227,7 +266,7 @@ fn run_ratatui_app(
terminal.clear()?;
let Cli { prompt, images, .. } = cli;
- let mut app = App::new(config.clone(), prompt, cli.skip_git_repo_check, images);
+ let mut app = App::new(config.clone(), prompt, images, should_show_trust_screen);
// Bridge log receiver into the AppEvent channel so latest log lines update the UI.
{
@@ -277,3 +316,39 @@ fn should_show_login_screen(config: &Config) -> bool {
false
}
}
+
+/// Determine if user has configured a sandbox / approval policy,
+/// or if the current cwd project is trusted, and updates the config
+/// accordingly.
+fn determine_repo_trust_state(
+ config: &mut Config,
+ config_toml: &ConfigToml,
+ approval_policy_overide: Option<AskForApproval>,
+ sandbox_mode_override: Option<SandboxMode>,
+ config_profile_override: Option<String>,
+) -> std::io::Result<bool> {
+ let config_profile = config_toml.get_config_profile(config_profile_override)?;
+
+ if approval_policy_overide.is_some() || sandbox_mode_override.is_some() {
+ // if the user has overridden either approval policy or sandbox mode,
+ // skip the trust flow
+ Ok(false)
+ } else if config_profile.approval_policy.is_some() {
+ // if the user has specified settings in a config profile, skip the trust flow
+ // todo: profile sandbox mode?
+ Ok(false)
+ } else if config_toml.approval_policy.is_some() || config_toml.sandbox_mode.is_some() {
+ // if the user has specified either approval policy or sandbox mode in config.toml
+ // skip the trust flow
+ Ok(false)
+ } else if config_toml.is_cwd_trusted(&config.cwd) {
+ // if the current cwd project is trusted and no config has been set
+ // skip the trust flow and set the approval policy and sandbox mode
+ config.approval_policy = AskForApproval::OnRequest;
+ config.sandbox_policy = SandboxPolicy::new_workspace_write_policy();
+ Ok(false)
+ } else {
+ // if none of the above conditions are met, show the trust screen
+ Ok(true)
+ }
+}
diff --git a/codex-rs/tui/src/onboarding/continue_to_chat.rs b/codex-rs/tui/src/onboarding/continue_to_chat.rs
index 071d0851da..01e31d900a 100644
--- a/codex-rs/tui/src/onboarding/continue_to_chat.rs
+++ b/codex-rs/tui/src/onboarding/continue_to_chat.rs
@@ -8,12 +8,14 @@ use crate::app_event_sender::AppEventSender;
use crate::onboarding::onboarding_screen::StepStateProvider;
use super::onboarding_screen::StepState;
+use std::sync::Arc;
+use std::sync::Mutex;
/// This doesn't render anything explicitly but serves as a signal that we made it to the end and
/// we should continue to the chat.
pub(crate) struct ContinueToChatWidget {
pub event_tx: AppEventSender,
- pub chat_widget_args: ChatWidgetArgs,
+ pub chat_widget_args: Arc<Mutex<ChatWidgetArgs>>,
}
impl StepStateProvider for ContinueToChatWidget {
@@ -24,7 +26,9 @@ impl StepStateProvider for ContinueToChatWidget {
impl WidgetRef for &ContinueToChatWidget {
fn render_ref(&self, _area: Rect, _buf: &mut Buffer) {
- self.event_tx
- .send(AppEvent::OnboardingComplete(self.chat_widget_args.clone()));
+ if let Ok(args) = self.chat_widget_args.lock() {
+ self.event_tx
+ .send(AppEvent::OnboardingComplete(args.clone()));
+ }
}
}
diff --git a/codex-rs/tui/src/onboarding/git_warning.rs b/codex-rs/tui/src/onboarding/git_warning.rs
deleted file mode 100644
index e4e5747404..0000000000
--- a/codex-rs/tui/src/onboarding/git_warning.rs
+++ /dev/null
@@ -1,126 +0,0 @@
-use std::path::PathBuf;
-
-use codex_core::util::is_inside_git_repo;
-use crossterm::event::KeyCode;
-use crossterm::event::KeyEvent;
-use ratatui::buffer::Buffer;
-use ratatui::layout::Rect;
-use ratatui::prelude::Widget;
-use ratatui::style::Modifier;
-use ratatui::style::Style;
-use ratatui::style::Stylize;
-use ratatui::text::Line;
-use ratatui::text::Span;
-use ratatui::widgets::Paragraph;
-use ratatui::widgets::WidgetRef;
-use ratatui::widgets::Wrap;
-
-use crate::app_event::AppEvent;
-use crate::app_event_sender::AppEventSender;
-use crate::colors::LIGHT_BLUE;
-
-use crate::onboarding::onboarding_screen::KeyboardHandler;
-use crate::onboarding::onboarding_screen::StepStateProvider;
-
-use super::onboarding_screen::StepState;
-
-pub(crate) struct GitWarningWidget {
- pub event_tx: AppEventSender,
- pub cwd: PathBuf,
- pub selection: Option<GitWarningSelection>,
- pub highlighted: GitWarningSelection,
-}
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub(crate) enum GitWarningSelection {
- Continue,
- Exit,
-}
-
-impl WidgetRef for &GitWarningWidget {
- fn render_ref(&self, area: Rect, buf: &mut Buffer) {
- let mut lines: Vec<Line> = vec![
- Line::from(vec![
- Span::raw("> "),
- Span::raw("You are running Codex in "),
- Span::styled(
- self.cwd.to_string_lossy().to_string(),
- Style::default().add_modifier(Modifier::BOLD),
- ),
- Span::raw(". This folder is not version controlled."),
- ]),
- Line::from(""),
- Line::from(" Do you want to continue?"),
- Line::from(""),
- ];
-
- let create_option =
- |idx: usize, option: GitWarningSelection, text: &str| -> Line<'static> {
- let is_selected = self.highlighted == option;
- if is_selected {
- Line::from(vec![
- Span::styled(
- format!("> {}. ", idx + 1),
- Style::default().fg(LIGHT_BLUE).add_modifier(Modifier::DIM),
- ),
- Span::styled(text.to_owned(), Style::default().fg(LIGHT_BLUE)),
- ])
- } else {
- Line::from(format!(" {}. {}", idx + 1, text))
- }
- };
-
- lines.push(create_option(0, GitWarningSelection::Continue, "Yes"));
- lines.push(create_option(1, GitWarningSelection::Exit, "No"));
- lines.push(Line::from(""));
- lines.push(Line::from(" Press Enter to continue").add_modifier(Modifier::DIM));
-
- Paragraph::new(lines)
- .wrap(Wrap { trim: false })
- .render(area, buf);
- }
-}
-
-impl KeyboardHandler for GitWarningWidget {
- fn handle_key_event(&mut self, key_event: KeyEvent) {
- match key_event.code {
- KeyCode::Up | KeyCode::Char('k') => {
- self.highlighted = GitWarningSelection::Continue;
- }
- KeyCode::Down | KeyCode::Char('j') => {
- self.highlighted = GitWarningSelection::Exit;
- }
- KeyCode::Char('1') => self.handle_continue(),
- KeyCode::Char('2') => self.handle_quit(),
- KeyCode::Enter => match self.highlighted {
- GitWarningSelection::Continue => self.handle_continue(),
- GitWarningSelection::Exit => self.handle_quit(),
- },
- _ => {}
- }
- }
-}
-
-impl StepStateProvider for GitWarningWidget {
- fn get_step_state(&self) -> StepState {
- let is_git_repo = is_inside_git_repo(&self.cwd);
- match is_git_repo {
- true => StepState::Hidden,
- false => match self.selection {
- Some(_) => StepState::Complete,
- None => StepState::InProgress,
- },
- }
- }
-}
-
-impl GitWarningWidget {
- fn handle_continue(&mut self) {
- self.selection = Some(GitWarningSelection::Continue);
- }
-
- fn handle_quit(&mut self) {
- self.highlighted = GitWarningSelection::Exit;
- self.event_tx.send(AppEvent::ExitRequest);
- }
-}
diff --git a/codex-rs/tui/src/onboarding/mod.rs b/codex-rs/tui/src/onboarding/mod.rs
index 645cda22d9..c116936851 100644
--- a/codex-rs/tui/src/onboarding/mod.rs
+++ b/codex-rs/tui/src/onboarding/mod.rs
@@ -1,5 +1,5 @@
mod auth;
mod continue_to_chat;
-mod git_warning;
pub mod onboarding_screen;
+mod trust_directory;
mod welcome;
diff --git a/codex-rs/tui/src/onboarding/onboarding_screen.rs b/codex-rs/tui/src/onboarding/onboarding_screen.rs
index 7ce7d16c47..a104f777c2 100644
--- a/codex-rs/tui/src/onboarding/onboarding_screen.rs
+++ b/codex-rs/tui/src/onboarding/onboarding_screen.rs
@@ -1,3 +1,4 @@
+use codex_core::util::is_inside_git_repo;
use crossterm::event::KeyEvent;
use ratatui::buffer::Buffer;
use ratatui::layout::Rect;
@@ -11,16 +12,18 @@ use crate::app_event_sender::AppEventSender;
use crate::onboarding::auth::AuthModeWidget;
use crate::onboarding::auth::SignInState;
use crate::onboarding::continue_to_chat::ContinueToChatWidget;
-use crate::onboarding::git_warning::GitWarningSelection;
-use crate::onboarding::git_warning::GitWarningWidget;
+use crate::onboarding::trust_directory::TrustDirectorySelection;
+use crate::onboarding::trust_directory::TrustDirectoryWidget;
use crate::onboarding::welcome::WelcomeWidget;
use std::path::PathBuf;
+use std::sync::Arc;
+use std::sync::Mutex;
#[allow(clippy::large_enum_variant)]
enum Step {
Welcome(WelcomeWidget),
Auth(AuthModeWidget),
- GitWarning(GitWarningWidget),
+ TrustDirectory(TrustDirectoryWidget),
ContinueToChat(ContinueToChatWidget),
}
@@ -49,7 +52,7 @@ pub(crate) struct OnboardingScreenArgs {
pub codex_home: PathBuf,
pub cwd: PathBuf,
pub show_login_screen: bool,
- pub show_git_warning: bool,
+ pub show_trust_screen: bool,
}
impl OnboardingScreen {
@@ -60,7 +63,7 @@ impl OnboardingScreen {
codex_home,
cwd,
show_login_screen,
- show_git_warning,
+ show_trust_screen,
} = args;
let mut steps: Vec<Step> = vec![Step::Welcome(WelcomeWidget {
is_logged_in: !show_login_screen,
@@ -71,20 +74,33 @@ impl OnboardingScreen {
highlighted_mode: AuthMode::ChatGPT,
error: None,
sign_in_state: SignInState::PickMode,
- codex_home,
+ codex_home: codex_home.clone(),
}))
}
- if show_git_warning {
- steps.push(Step::GitWarning(GitWarningWidget {
- event_tx: event_tx.clone(),
+ let is_git_repo = is_inside_git_repo(&cwd);
+ let highlighted = if is_git_repo {
+ TrustDirectorySelection::Trust
+ } else {
+ // Default to not trusting the directory if it's not a git repo.
+ TrustDirectorySelection::DontTrust
+ };
+ // Share ChatWidgetArgs between steps so changes in the TrustDirectory step
+ // are reflected when continuing to chat.
+ let shared_chat_args = Arc::new(Mutex::new(chat_widget_args));
+ if show_trust_screen {
+ steps.push(Step::TrustDirectory(TrustDirectoryWidget {
cwd,
+ codex_home,
+ is_git_repo,
selection: None,
- highlighted: GitWarningSelection::Continue,
+ highlighted,
+ error: None,
+ chat_widget_args: shared_chat_args.clone(),
}))
}
steps.push(Step::ContinueToChat(ContinueToChatWidget {
event_tx: event_tx.clone(),
- chat_widget_args,
+ chat_widget_args: shared_chat_args,
}));
// TODO: add git warning.
Self { event_tx, steps }
@@ -215,7 +231,7 @@ impl KeyboardHandler for Step {
match self {
Step::Welcome(_) | Step::ContinueToChat(_) => (),
Step::Auth(widget) => widget.handle_key_event(key_event),
- Step::GitWarning(widget) => widget.handle_key_event(key_event),
+ Step::TrustDirectory(widget) => widget.handle_key_event(key_event),
}
}
}
@@ -225,7 +241,7 @@ impl StepStateProvider for Step {
match self {
Step::Welcome(w) => w.get_step_state(),
Step::Auth(w) => w.get_step_state(),
- Step::GitWarning(w) => w.get_step_state(),
+ Step::TrustDirectory(w) => w.get_step_state(),
Step::ContinueToChat(w) => w.get_step_state(),
}
}
@@ -240,7 +256,7 @@ impl WidgetRef for Step {
Step::Auth(widget) => {
widget.render_ref(area, buf);
}
- Step::GitWarning(widget) => {
+ Step::TrustDirectory(widget) => {
widget.render_ref(area, buf);
}
Step::ContinueToChat(widget) => {
diff --git a/codex-rs/tui/src/onboarding/trust_directory.rs b/codex-rs/tui/src/onboarding/trust_directory.rs
new file mode 100644
index 0000000000..3be9bac1ac
--- /dev/null
+++ b/codex-rs/tui/src/onboarding/trust_directory.rs
@@ -0,0 +1,179 @@
+use std::path::PathBuf;
+
+use codex_core::config::set_project_trusted;
+use codex_core::protocol::AskForApproval;
+use codex_core::protocol::SandboxPolicy;
+use crossterm::event::KeyCode;
+use crossterm::event::KeyEvent;
+use ratatui::buffer::Buffer;
+use ratatui::layout::Rect;
+use ratatui::prelude::Widget;
+use ratatui::style::Color;
+use ratatui::style::Modifier;
+use ratatui::style::Style;
+use ratatui::style::Stylize;
+use ratatui::text::Line;
+use ratatui::text::Span;
+use ratatui::widgets::Paragraph;
+use ratatui::widgets::WidgetRef;
+use ratatui::widgets::Wrap;
+
+use crate::colors::LIGHT_BLUE;
+
+use crate::onboarding::onboarding_screen::KeyboardHandler;
+use crate::onboarding::onboarding_screen::StepStateProvider;
+
+use super::onboarding_screen::StepState;
+use crate::app::ChatWidgetArgs;
+use std::sync::Arc;
+use std::sync::Mutex;
+
+pub(crate) struct TrustDirectoryWidget {
+ pub codex_home: PathBuf,
+ pub cwd: PathBuf,
+ pub is_git_repo: bool,
+ pub selection: Option<TrustDirectorySelection>,
+ pub highlighted: TrustDirectorySelection,
+ pub error: Option<String>,
+ pub chat_widget_args: Arc<Mutex<ChatWidgetArgs>>,
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub(crate) enum TrustDirectorySelection {
+ Trust,
+ DontTrust,
+}
+
+impl WidgetRef for &TrustDirectoryWidget {
+ fn render_ref(&self, area: Rect, buf: &mut Buffer) {
+ let mut lines: Vec<Line> = vec![
+ Line::from(vec![
+ Span::raw("> "),
+ Span::styled(
+ "You are running Codex in ",
+ Style::default().add_modifier(Modifier::BOLD),
+ ),
+ Span::raw(self.cwd.to_string_lossy().to_string()),
+ ]),
+ Line::from(""),
+ ];
+
+ if self.is_git_repo {
+ lines.push(Line::from(
+ " Since this folder is version controlled, you may wish to allow Codex",
+ ));
+ lines.push(Line::from(
+ " to work in this folder without asking for approval.",
+ ));
+ } else {
+ lines.push(Line::from(
+ " Since this folder is not version controlled, we recommend requiring",
+ ));
+ lines.push(Line::from(" approval of all edits and commands."));
+ }
+ lines.push(Line::from(""));
+
+ let create_option =
+ |idx: usize, option: TrustDirectorySelection, text: &str| -> Line<'static> {
+ let is_selected = self.highlighted == option;
+ if is_selected {
+ Line::from(vec![
+ Span::styled(
+ format!("> {}. ", idx + 1),
+ Style::default().fg(LIGHT_BLUE).add_modifier(Modifier::DIM),
+ ),
+ Span::styled(text.to_owned(), Style::default().fg(LIGHT_BLUE)),
+ ])
+ } else {
+ Line::from(format!(" {}. {}", idx + 1, text))
+ }
+ };
+
+ if self.is_git_repo {
+ lines.push(create_option(
+ 0,
+ TrustDirectorySelection::Trust,
+ "Yes, allow Codex to work in this folder without asking for approval",
+ ));
+ lines.push(create_option(
+ 1,
+ TrustDirectorySelection::DontTrust,
+ "No, ask me to approve edits and commands",
+ ));
+ } else {
+ lines.push(create_option(
+ 0,
+ TrustDirectorySelection::Trust,
+ "Allow Codex to work in this folder without asking for approval",
+ ));
+ lines.push(create_option(
+ 1,
+ TrustDirectorySelection::DontTrust,
+ "Require approval of edits and commands",
+ ));
+ }
+ lines.push(Line::from(""));
+ if let Some(error) = &self.error {
+ lines.push(Line::from(format!(" {error}")).fg(Color::Red));
+ lines.push(Line::from(""));
+ }
+ lines.push(Line::from(" Press Enter to continue").add_modifier(Modifier::DIM));
+
+ Paragraph::new(lines)
+ .wrap(Wrap { trim: false })
+ .render(area, buf);
+ }
+}
+
+impl KeyboardHandler for TrustDirectoryWidget {
+ fn handle_key_event(&mut self, key_event: KeyEvent) {
+ match key_event.code {
+ KeyCode::Up | KeyCode::Char('k') => {
+ self.highlighted = TrustDirectorySelection::Trust;
+ }
+ KeyCode::Down | KeyCode::Char('j') => {
+ self.highlighted = TrustDirectorySelection::DontTrust;
+ }
+ KeyCode::Char('1') => self.handle_trust(),
+ KeyCode::Char('2') => self.handle_dont_trust(),
+ KeyCode::Enter => match self.highlighted {
+ TrustDirectorySelection::Trust => self.handle_trust(),
+ TrustDirectorySelection::DontTrust => self.handle_dont_trust(),
+ },
+ _ => {}
+ }
+ }
+}
+
+impl StepStateProvider for TrustDirectoryWidget {
+ fn get_step_state(&self) -> StepState {
+ match self.selection {
+ Some(_) => StepState::Complete,
+ None => StepState::InProgress,
+ }
+ }
+}
+
+impl TrustDirectoryWidget {
+ fn handle_trust(&mut self) {
+ if let Err(e) = set_project_trusted(&self.codex_home, &self.cwd) {
+ tracing::error!("Failed to set project trusted: {e:?}");
+ self.error = Some(e.to_string());
+ // self.error = Some("Failed to set project trusted".to_string());
+ }
+
+ // Update the in-memory chat config for this session to a more permissive
+ // policy suitable for a trusted workspace.
+ if let Ok(mut args) = self.chat_widget_args.lock() {
+ args.config.approval_policy = AskForApproval::OnRequest;
+ args.config.sandbox_policy = SandboxPolicy::new_workspace_write_policy();
+ }
+
+ self.selection = Some(TrustDirectorySelection::Trust);
+ }
+
+ fn handle_dont_trust(&mut self) {
+ self.highlighted = TrustDirectorySelection::DontTrust;
+ self.selection = Some(TrustDirectorySelection::DontTrust);
+ }
+}
Review Comments
codex-rs/core/Cargo.toml
- Created: 2025-08-07 15:46:51 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2260738622
@@ -53,6 +53,8 @@ tree-sitter-bash = "0.25.0"
uuid = { version = "1", features = ["serde", "v4"] }
whoami = "1.6.0"
wildmatch = "2.4.0"
+toml_edit = "0.23.3"
alpha :P
codex-rs/core/src/config.rs
- Created: 2025-08-07 09:11:39 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2259655004
@@ -214,6 +225,33 @@ fn load_config_as_toml(codex_home: &Path) -> std::io::Result<TomlValue> {
}
}
+/// Patch `CODEX_HOME/config.toml` project state.
+/// Use with caution.
+pub fn set_project_trusted(
+ codex_home: &Path,
+ project_path: &Path,
+ trusted: bool,
+) -> anyhow::Result<()> {
+ let config_path = codex_home.join("config.toml");
+
+ // Parse existing config if present; otherwise start a new document.
+ let mut doc = match std::fs::read_to_string(&config_path) {
+ Ok(s) => s.parse::<DocumentMut>()?,
+ Err(e) if e.kind() == std::io::ErrorKind::NotFound => DocumentMut::new(),
+ Err(e) => return Err(e.into()),
+ };
+
+ let project_key = project_path.to_string_lossy().to_string();
+ doc["projects"][project_key.as_str()]["trusted"] = toml_edit::value(trusted);
+
+ if let Some(parent) = config_path.parent() {
+ std::fs::create_dir_all(parent)?;
+ }
+ std::fs::write(config_path, doc.to_string())?;
We should maybe write to a temp file in the folder and then
mvit so writes are atomic.
- Created: 2025-08-07 09:12:03 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2259656059
@@ -214,6 +225,33 @@ fn load_config_as_toml(codex_home: &Path) -> std::io::Result<TomlValue> {
}
}
+/// Patch `CODEX_HOME/config.toml` project state.
+/// Use with caution.
+pub fn set_project_trusted(
+ codex_home: &Path,
+ project_path: &Path,
+ trusted: bool,
+) -> anyhow::Result<()> {
+ let config_path = codex_home.join("config.toml");
+
+ // Parse existing config if present; otherwise start a new document.
+ let mut doc = match std::fs::read_to_string(&config_path) {
+ Ok(s) => s.parse::<DocumentMut>()?,
+ Err(e) if e.kind() == std::io::ErrorKind::NotFound => DocumentMut::new(),
+ Err(e) => return Err(e.into()),
+ };
+
+ let project_key = project_path.to_string_lossy().to_string();
+ doc["projects"][project_key.as_str()]["trusted"] = toml_edit::value(trusted);
+
+ if let Some(parent) = config_path.parent() {
+ std::fs::create_dir_all(parent)?;
+ }
By construction,
parentiscodex_home, so just use that?
- Created: 2025-08-07 09:12:51 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2259658005
@@ -214,6 +225,33 @@ fn load_config_as_toml(codex_home: &Path) -> std::io::Result<TomlValue> {
}
}
+/// Patch `CODEX_HOME/config.toml` project state.
+/// Use with caution.
+pub fn set_project_trusted(
+ codex_home: &Path,
+ project_path: &Path,
+ trusted: bool,
+) -> anyhow::Result<()> {
+ let config_path = codex_home.join("config.toml");
+
+ // Parse existing config if present; otherwise start a new document.
+ let mut doc = match std::fs::read_to_string(&config_path) {
+ Ok(s) => s.parse::<DocumentMut>()?,
+ Err(e) if e.kind() == std::io::ErrorKind::NotFound => DocumentMut::new(),
+ Err(e) => return Err(e.into()),
+ };
+
+ let project_key = project_path.to_string_lossy().to_string();
+ doc["projects"][project_key.as_str()]["trusted"] = toml_edit::value(trusted);
What happens if
docdoes not have a"projects"key: does it create an entry on demand? How does this not panic?
- Created: 2025-08-07 09:18:05 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2259670414
@@ -518,6 +565,41 @@ impl Config {
Self::get_base_instructions(experimental_instructions_path, &resolved_cwd)?;
let base_instructions = base_instructions.or(file_base_instructions);
+ // Let's begin.
+ // First: load approval_policy and sandbox_mode from overrides, config
+ // profile, or config.toml.
+ let mut approval_policy = approval_policy
+ .or(config_profile.approval_policy)
+ .or(cfg.approval_policy.clone());
+ // TODO: Add sandbox_mode to the config profile? Doesn't
+ // appear to exist right now
+ let mut sandbox_mode = sandbox_mode.or(cfg.sandbox_mode.clone());
+ let mut projects = cfg.projects.clone().unwrap_or_default();
+
+ // Second: Default to "trusted" if the user has configured approval policy
What does "trusted" mean, particularly if sandbox mode is "read only"?
- Created: 2025-08-07 09:19:15 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2259673214
@@ -518,6 +565,41 @@ impl Config {
Self::get_base_instructions(experimental_instructions_path, &resolved_cwd)?;
let base_instructions = base_instructions.or(file_base_instructions);
+ // Let's begin.
+ // First: load approval_policy and sandbox_mode from overrides, config
+ // profile, or config.toml.
+ let mut approval_policy = approval_policy
+ .or(config_profile.approval_policy)
+ .or(cfg.approval_policy.clone());
+ // TODO: Add sandbox_mode to the config profile? Doesn't
+ // appear to exist right now
+ let mut sandbox_mode = sandbox_mode.or(cfg.sandbox_mode.clone());
+ let mut projects = cfg.projects.clone().unwrap_or_default();
+
+ // Second: Default to "trusted" if the user has configured approval policy
+ // or sandbox mode, regardless of projects config. If you've modified the
+ // config.toml, we'll respect your decision.
+ //
+ // This is a bit of a hack, but it allows us to skip tui onboarding
+ // for now.
+ if approval_policy.is_some() || sandbox_mode.is_some() {
+ projects.insert(
What if
projectshas an existing entry forcwdwheretrusted=false? Should this override it? Should it write it back?
- Created: 2025-08-07 09:30:40 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2259702973
@@ -214,6 +225,33 @@ fn load_config_as_toml(codex_home: &Path) -> std::io::Result<TomlValue> {
}
}
+/// Patch `CODEX_HOME/config.toml` project state.
+/// Use with caution.
+pub fn set_project_trusted(
+ codex_home: &Path,
+ project_path: &Path,
+ trusted: bool,
+) -> anyhow::Result<()> {
+ let config_path = codex_home.join("config.toml");
+
+ // Parse existing config if present; otherwise start a new document.
+ let mut doc = match std::fs::read_to_string(&config_path) {
+ Ok(s) => s.parse::<DocumentMut>()?,
+ Err(e) if e.kind() == std::io::ErrorKind::NotFound => DocumentMut::new(),
+ Err(e) => return Err(e.into()),
+ };
+
+ let project_key = project_path.to_string_lossy().to_string();
+ doc["projects"][project_key.as_str()]["trusted"] = toml_edit::value(trusted);
I see,
DocumentMutimplementsIndexMut<&str>.
- Created: 2025-08-07 09:34:45 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2259712484
@@ -518,6 +565,41 @@ impl Config {
Self::get_base_instructions(experimental_instructions_path, &resolved_cwd)?;
let base_instructions = base_instructions.or(file_base_instructions);
+ // Let's begin.
+ // First: load approval_policy and sandbox_mode from overrides, config
+ // profile, or config.toml.
+ let mut approval_policy = approval_policy
+ .or(config_profile.approval_policy)
+ .or(cfg.approval_policy);
+ // TODO: Add sandbox_mode to the config profile? Doesn't
+ // appear to exist right now
+ let mut sandbox_mode = sandbox_mode.or(cfg.sandbox_mode);
+ let mut projects = cfg.projects.clone().unwrap_or_default();
+
+ // Second: Default to "trusted" if the user has configured approval policy
+ // or sandbox mode, regardless of projects config. If you've modified the
+ // config.toml, we'll respect your decision.
+ //
+ // This is a bit of a hack, but it allows us to skip tui onboarding
+ // for now.
+ if approval_policy.is_some() || sandbox_mode.is_some() {
+ projects.insert(
+ resolved_cwd.to_string_lossy().to_string(),
+ ProjectConfig {
+ trusted: Some(true),
+ },
+ );
+ } else if let Some(project) = projects.get(&resolved_cwd.to_string_lossy().to_string()) {
+ // Third: If we have a project config, and it's trusted, set the approval policy and sandbox mode
+ if project.trusted.unwrap_or(false) {
+ approval_policy = Some(AskForApproval::OnRequest);
+ sandbox_mode = Some(SandboxMode::WorkspaceWrite);
So if I run the Codex CLI once with
--sandbox read-only, we would set it as trusted, and the next time, if I didn't run any flags, we would run it with workplace-write?
- Created: 2025-08-07 09:40:22 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2259725983
@@ -518,6 +565,41 @@ impl Config {
Self::get_base_instructions(experimental_instructions_path, &resolved_cwd)?;
let base_instructions = base_instructions.or(file_base_instructions);
+ // Let's begin.
+ // First: load approval_policy and sandbox_mode from overrides, config
+ // profile, or config.toml.
+ let mut approval_policy = approval_policy
+ .or(config_profile.approval_policy)
+ .or(cfg.approval_policy.clone());
+ // TODO: Add sandbox_mode to the config profile? Doesn't
+ // appear to exist right now
+ let mut sandbox_mode = sandbox_mode.or(cfg.sandbox_mode.clone());
+ let mut projects = cfg.projects.clone().unwrap_or_default();
+
+ // Second: Default to "trusted" if the user has configured approval policy
What if I ran
codex exec --sandbox read-only?
- Created: 2025-08-07 09:40:56 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2259727288
@@ -518,6 +565,41 @@ impl Config {
Self::get_base_instructions(experimental_instructions_path, &resolved_cwd)?;
let base_instructions = base_instructions.or(file_base_instructions);
+ // Let's begin.
+ // First: load approval_policy and sandbox_mode from overrides, config
+ // profile, or config.toml.
+ let mut approval_policy = approval_policy
+ .or(config_profile.approval_policy)
+ .or(cfg.approval_policy.clone());
+ // TODO: Add sandbox_mode to the config profile? Doesn't
+ // appear to exist right now
+ let mut sandbox_mode = sandbox_mode.or(cfg.sandbox_mode.clone());
+ let mut projects = cfg.projects.clone().unwrap_or_default();
+
+ // Second: Default to "trusted" if the user has configured approval policy
This logic is in
core, but it seems you are making strong assumptions about the UI here?
- Created: 2025-08-07 15:49:16 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2260744829
@@ -666,6 +715,28 @@ pub fn find_codex_home() -> std::io::Result<PathBuf> {
Ok(p)
}
+pub fn resolve_cwd(cwd: Option<PathBuf>) -> std::io::Result<PathBuf> {
I think this function can go away?
codex-rs/core/src/protocol.rs
- Created: 2025-08-07 09:20:26 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2259675989
@@ -151,6 +150,7 @@ pub enum AskForApproval {
OnFailure,
/// The model decides when to ask the user for approval.
+ #[default]
Update docs?
codex-rs/exec/src/lib.rs
- Created: 2025-08-07 09:21:30 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2259679636
@@ -180,11 +178,6 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
// is using.
event_processor.print_config_summary(&config, &prompt);
- if !skip_git_repo_check && !is_inside_git_repo(&config.cwd.to_path_buf()) {
I thought there was still going to be a case where we exit for
codex execeven if not in a Git repo?
codex-rs/tui/src/lib.rs
- Created: 2025-08-07 15:48:51 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2260743769
@@ -277,3 +322,33 @@ fn should_show_login_screen(config: &Config) -> bool {
false
}
}
+
+fn should_show_trust_screen(
+ config: &mut Config,
+ config_toml: &ConfigToml,
+ approval_policy_overide: Option<AskForApproval>,
+ sandbox_mode_override: Option<SandboxMode>,
+ cwd: Option<PathBuf>,
Why does this take
cwdas an arg instead of usingconfig.cwd?We should be sure we are honoring
--cwdif the user passed it in...
- Created: 2025-08-07 15:52:25 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2260751522
@@ -277,3 +322,33 @@ fn should_show_login_screen(config: &Config) -> bool {
false
}
}
+
+fn should_show_trust_screen(
+ config: &mut Config,
+ config_toml: &ConfigToml,
+ approval_policy_overide: Option<AskForApproval>,
+ sandbox_mode_override: Option<SandboxMode>,
+ cwd: Option<PathBuf>,
+) -> std::io::Result<bool> {
+ let cwd = cwd.map(|p| p.canonicalize().unwrap_or(p));
+ let resolved_cwd = resolve_cwd(cwd)?;
+
+ if approval_policy_overide.is_some() || sandbox_mode_override.is_some() {
Note the user could have passed
-c sandbox_mode=read-only...
codex-rs/tui/src/onboarding/continue_to_chat.rs
- Created: 2025-08-07 09:24:44 UTC | Link: https://github.com/openai/codex/pull/1929#discussion_r2259687584
@@ -8,12 +8,14 @@ use crate::app_event_sender::AppEventSender;
use crate::onboarding::onboarding_screen::StepStateProvider;
use super::onboarding_screen::StepState;
+use std::sync::Arc;
+use std::sync::Mutex;
/// This doesn't render anything explicitly but serves as a signal that we made it to the end and
/// we should continue to the chat.
pub(crate) struct ContinueToChatWidget {
pub event_tx: AppEventSender,
- pub chat_widget_args: ChatWidgetArgs,
+ pub chat_widget_args: Arc<Mutex<ChatWidgetArgs>>,
Maybe I'm missing where this happens, but I don't see where this is mutated (though it is cloned), so does it need
Arc<Mutex>?