diff --git a/codex-rs/common/src/approval_presets.rs b/codex-rs/common/src/approval_presets.rs index 6dc0cff0..6c3bf395 100644 --- a/codex-rs/common/src/approval_presets.rs +++ b/codex-rs/common/src/approval_presets.rs @@ -24,21 +24,21 @@ pub fn builtin_approval_presets() -> Vec { ApprovalPreset { id: "read-only", label: "Read Only", - description: "Codex can read files and answer questions. Codex requires approval to make edits, run commands, or access network", + description: "Codex can read files and answer questions. Codex requires approval to make edits, run commands, or access network.", approval: AskForApproval::OnRequest, sandbox: SandboxPolicy::ReadOnly, }, ApprovalPreset { id: "auto", label: "Auto", - description: "Codex can read files, make edits, and run commands in the workspace. Codex requires approval to work outside the workspace or access network", + description: "Codex can read files, make edits, and run commands in the workspace. Codex requires approval to work outside the workspace or access network.", approval: AskForApproval::OnRequest, sandbox: SandboxPolicy::new_workspace_write_policy(), }, ApprovalPreset { id: "full-access", label: "Full Access", - description: "Codex can read files, make edits, and run commands with network access, without approval. Exercise caution", + description: "Codex can read files, make edits, and run commands with network access, without approval. Exercise caution.", approval: AskForApproval::Never, sandbox: SandboxPolicy::DangerFullAccess, }, diff --git a/codex-rs/core/src/config.rs b/codex-rs/core/src/config.rs index c5406b1d..38097898 100644 --- a/codex-rs/core/src/config.rs +++ b/codex-rs/core/src/config.rs @@ -108,6 +108,10 @@ pub struct Config { /// for either of approval_policy or sandbox_mode. pub did_user_set_custom_approval_policy_or_sandbox_mode: bool, + /// On Windows, indicates that a previously configured workspace-write sandbox + /// was coerced to read-only because native auto mode is unsupported. + pub forced_auto_mode_downgraded_on_windows: bool, + pub shell_environment_policy: ShellEnvironmentPolicy, /// When `true`, `AgentReasoning` events emitted by the backend will be @@ -1022,6 +1026,12 @@ impl From for Tools { } } +#[derive(Debug, PartialEq, Eq)] +pub struct SandboxPolicyResolution { + pub policy: SandboxPolicy, + pub forced_auto_mode_downgraded_on_windows: bool, +} + impl ConfigToml { /// Derive the effective sandbox policy from the configuration. fn derive_sandbox_policy( @@ -1029,7 +1039,7 @@ impl ConfigToml { sandbox_mode_override: Option, profile_sandbox_mode: Option, resolved_cwd: &Path, - ) -> SandboxPolicy { + ) -> SandboxPolicyResolution { let resolved_sandbox_mode = sandbox_mode_override .or(profile_sandbox_mode) .or(self.sandbox_mode) @@ -1044,7 +1054,7 @@ impl ConfigToml { }) }) .unwrap_or_default(); - match resolved_sandbox_mode { + let mut sandbox_policy = match resolved_sandbox_mode { SandboxMode::ReadOnly => SandboxPolicy::new_read_only_policy(), SandboxMode::WorkspaceWrite => match self.sandbox_workspace_write.as_ref() { Some(SandboxWorkspaceWrite { @@ -1061,6 +1071,17 @@ impl ConfigToml { None => SandboxPolicy::new_workspace_write_policy(), }, SandboxMode::DangerFullAccess => SandboxPolicy::DangerFullAccess, + }; + let mut forced_auto_mode_downgraded_on_windows = false; + if cfg!(target_os = "windows") + && matches!(resolved_sandbox_mode, SandboxMode::WorkspaceWrite) + { + sandbox_policy = SandboxPolicy::new_read_only_policy(); + forced_auto_mode_downgraded_on_windows = true; + } + SandboxPolicyResolution { + policy: sandbox_policy, + forced_auto_mode_downgraded_on_windows, } } @@ -1221,8 +1242,10 @@ impl Config { .get_active_project(&resolved_cwd) .unwrap_or(ProjectConfig { trust_level: None }); - let mut sandbox_policy = - cfg.derive_sandbox_policy(sandbox_mode, config_profile.sandbox_mode, &resolved_cwd); + let SandboxPolicyResolution { + policy: mut sandbox_policy, + forced_auto_mode_downgraded_on_windows, + } = cfg.derive_sandbox_policy(sandbox_mode, config_profile.sandbox_mode, &resolved_cwd); if let SandboxPolicy::WorkspaceWrite { writable_roots, .. } = &mut sandbox_policy { for path in additional_writable_roots { if !writable_roots.iter().any(|existing| existing == &path) { @@ -1353,6 +1376,7 @@ impl Config { approval_policy, sandbox_policy, did_user_set_custom_approval_policy_or_sandbox_mode, + forced_auto_mode_downgraded_on_windows, shell_environment_policy, notify: cfg.notify, user_instructions, @@ -1604,13 +1628,17 @@ network_access = false # This should be ignored. let sandbox_full_access_cfg = toml::from_str::(sandbox_full_access) .expect("TOML deserialization should succeed"); let sandbox_mode_override = None; + let resolution = sandbox_full_access_cfg.derive_sandbox_policy( + sandbox_mode_override, + None, + &PathBuf::from("/tmp/test"), + ); assert_eq!( - SandboxPolicy::DangerFullAccess, - sandbox_full_access_cfg.derive_sandbox_policy( - sandbox_mode_override, - None, - &PathBuf::from("/tmp/test") - ) + resolution, + SandboxPolicyResolution { + policy: SandboxPolicy::DangerFullAccess, + forced_auto_mode_downgraded_on_windows: false, + } ); let sandbox_read_only = r#" @@ -1623,13 +1651,17 @@ network_access = true # This should be ignored. let sandbox_read_only_cfg = toml::from_str::(sandbox_read_only) .expect("TOML deserialization should succeed"); let sandbox_mode_override = None; + let resolution = sandbox_read_only_cfg.derive_sandbox_policy( + sandbox_mode_override, + None, + &PathBuf::from("/tmp/test"), + ); assert_eq!( - SandboxPolicy::ReadOnly, - sandbox_read_only_cfg.derive_sandbox_policy( - sandbox_mode_override, - None, - &PathBuf::from("/tmp/test") - ) + resolution, + SandboxPolicyResolution { + policy: SandboxPolicy::ReadOnly, + forced_auto_mode_downgraded_on_windows: false, + } ); let sandbox_workspace_write = r#" @@ -1646,19 +1678,33 @@ exclude_slash_tmp = true let sandbox_workspace_write_cfg = toml::from_str::(sandbox_workspace_write) .expect("TOML deserialization should succeed"); let sandbox_mode_override = None; - assert_eq!( - SandboxPolicy::WorkspaceWrite { - writable_roots: vec![PathBuf::from("/my/workspace")], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }, - sandbox_workspace_write_cfg.derive_sandbox_policy( - sandbox_mode_override, - None, - &PathBuf::from("/tmp/test") - ) + let resolution = sandbox_workspace_write_cfg.derive_sandbox_policy( + sandbox_mode_override, + None, + &PathBuf::from("/tmp/test"), ); + if cfg!(target_os = "windows") { + assert_eq!( + resolution, + SandboxPolicyResolution { + policy: SandboxPolicy::ReadOnly, + forced_auto_mode_downgraded_on_windows: true, + } + ); + } else { + assert_eq!( + resolution, + SandboxPolicyResolution { + policy: SandboxPolicy::WorkspaceWrite { + writable_roots: vec![PathBuf::from("/my/workspace")], + network_access: false, + exclude_tmpdir_env_var: true, + exclude_slash_tmp: true, + }, + forced_auto_mode_downgraded_on_windows: false, + } + ); + } let sandbox_workspace_write = r#" sandbox_mode = "workspace-write" @@ -1677,19 +1723,33 @@ trust_level = "trusted" let sandbox_workspace_write_cfg = toml::from_str::(sandbox_workspace_write) .expect("TOML deserialization should succeed"); let sandbox_mode_override = None; - assert_eq!( - SandboxPolicy::WorkspaceWrite { - writable_roots: vec![PathBuf::from("/my/workspace")], - network_access: false, - exclude_tmpdir_env_var: true, - exclude_slash_tmp: true, - }, - sandbox_workspace_write_cfg.derive_sandbox_policy( - sandbox_mode_override, - None, - &PathBuf::from("/tmp/test") - ) + let resolution = sandbox_workspace_write_cfg.derive_sandbox_policy( + sandbox_mode_override, + None, + &PathBuf::from("/tmp/test"), ); + if cfg!(target_os = "windows") { + assert_eq!( + resolution, + SandboxPolicyResolution { + policy: SandboxPolicy::ReadOnly, + forced_auto_mode_downgraded_on_windows: true, + } + ); + } else { + assert_eq!( + resolution, + SandboxPolicyResolution { + policy: SandboxPolicy::WorkspaceWrite { + writable_roots: vec![PathBuf::from("/my/workspace")], + network_access: false, + exclude_tmpdir_env_var: true, + exclude_slash_tmp: true, + }, + forced_auto_mode_downgraded_on_windows: false, + } + ); + } } #[test] @@ -1714,19 +1774,30 @@ trust_level = "trusted" )?; let expected_backend = canonicalize(&backend).expect("canonicalize backend directory"); - match config.sandbox_policy { - SandboxPolicy::WorkspaceWrite { writable_roots, .. } => { - assert_eq!( - writable_roots - .iter() - .filter(|root| **root == expected_backend) - .count(), - 1, - "expected single writable root entry for {}", - expected_backend.display() - ); + if cfg!(target_os = "windows") { + assert!( + config.forced_auto_mode_downgraded_on_windows, + "expected workspace-write request to be downgraded on Windows" + ); + match config.sandbox_policy { + SandboxPolicy::ReadOnly => {} + other => panic!("expected read-only policy on Windows, got {other:?}"), + } + } else { + match config.sandbox_policy { + SandboxPolicy::WorkspaceWrite { writable_roots, .. } => { + assert_eq!( + writable_roots + .iter() + .filter(|root| **root == expected_backend) + .count(), + 1, + "expected single writable root entry for {}", + expected_backend.display() + ); + } + other => panic!("expected workspace-write policy, got {other:?}"), } - other => panic!("expected workspace-write policy, got {other:?}"), } Ok(()) @@ -1841,10 +1912,16 @@ trust_level = "trusted" codex_home.path().to_path_buf(), )?; - assert!(matches!( - config.sandbox_policy, - SandboxPolicy::WorkspaceWrite { .. } - )); + if cfg!(target_os = "windows") { + assert!(matches!(config.sandbox_policy, SandboxPolicy::ReadOnly)); + assert!(config.forced_auto_mode_downgraded_on_windows); + } else { + assert!(matches!( + config.sandbox_policy, + SandboxPolicy::WorkspaceWrite { .. } + )); + assert!(!config.forced_auto_mode_downgraded_on_windows); + } Ok(()) } @@ -2943,6 +3020,7 @@ model_verbosity = "high" approval_policy: AskForApproval::Never, sandbox_policy: SandboxPolicy::new_read_only_policy(), did_user_set_custom_approval_policy_or_sandbox_mode: true, + forced_auto_mode_downgraded_on_windows: false, shell_environment_policy: ShellEnvironmentPolicy::default(), user_instructions: None, notify: None, @@ -3012,6 +3090,7 @@ model_verbosity = "high" approval_policy: AskForApproval::UnlessTrusted, sandbox_policy: SandboxPolicy::new_read_only_policy(), did_user_set_custom_approval_policy_or_sandbox_mode: true, + forced_auto_mode_downgraded_on_windows: false, shell_environment_policy: ShellEnvironmentPolicy::default(), user_instructions: None, notify: None, @@ -3096,6 +3175,7 @@ model_verbosity = "high" approval_policy: AskForApproval::OnFailure, sandbox_policy: SandboxPolicy::new_read_only_policy(), did_user_set_custom_approval_policy_or_sandbox_mode: true, + forced_auto_mode_downgraded_on_windows: false, shell_environment_policy: ShellEnvironmentPolicy::default(), user_instructions: None, notify: None, @@ -3166,6 +3246,7 @@ model_verbosity = "high" approval_policy: AskForApproval::OnFailure, sandbox_policy: SandboxPolicy::new_read_only_policy(), did_user_set_custom_approval_policy_or_sandbox_mode: true, + forced_auto_mode_downgraded_on_windows: false, shell_environment_policy: ShellEnvironmentPolicy::default(), user_instructions: None, notify: None, diff --git a/codex-rs/exec/tests/suite/resume.rs b/codex-rs/exec/tests/suite/resume.rs index 320c5766..24b8cb0b 100644 --- a/codex-rs/exec/tests/suite/resume.rs +++ b/codex-rs/exec/tests/suite/resume.rs @@ -234,10 +234,17 @@ fn exec_resume_preserves_cli_configuration_overrides() -> anyhow::Result<()> { stderr.contains("model: gpt-5-high"), "stderr missing model override: {stderr}" ); - assert!( - stderr.contains("sandbox: workspace-write"), - "stderr missing sandbox override: {stderr}" - ); + if cfg!(target_os = "windows") { + assert!( + stderr.contains("sandbox: read-only"), + "stderr missing downgraded sandbox note: {stderr}" + ); + } else { + assert!( + stderr.contains("sandbox: workspace-write"), + "stderr missing sandbox override: {stderr}" + ); + } let resumed_path = find_session_file_containing_marker(&sessions_dir, &marker2) .expect("no resumed session file containing marker2"); diff --git a/codex-rs/tui/src/app.rs b/codex-rs/tui/src/app.rs index c8eab729..8fc18a1d 100644 --- a/codex-rs/tui/src/app.rs +++ b/codex-rs/tui/src/app.rs @@ -369,6 +369,9 @@ impl App { AppEvent::OpenFeedbackConsent { category } => { self.chat_widget.open_feedback_consent(category); } + AppEvent::ShowWindowsAutoModeInstructions => { + self.chat_widget.open_windows_auto_mode_instructions(); + } AppEvent::PersistModelSelection { model, effort } => { let profile = self.active_profile.as_deref(); match persist_model_selection(&self.config.codex_home, profile, &model, effort) diff --git a/codex-rs/tui/src/app_event.rs b/codex-rs/tui/src/app_event.rs index 8b14b0be..514e2347 100644 --- a/codex-rs/tui/src/app_event.rs +++ b/codex-rs/tui/src/app_event.rs @@ -72,6 +72,9 @@ pub(crate) enum AppEvent { preset: ApprovalPreset, }, + /// Show Windows Subsystem for Linux setup instructions for auto mode. + ShowWindowsAutoModeInstructions, + /// Update the current approval policy in the running app and widget. UpdateAskForApprovalPolicy(AskForApproval), diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 13411df4..f5966605 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -88,6 +88,8 @@ use crate::history_cell::AgentMessageCell; use crate::history_cell::HistoryCell; use crate::history_cell::McpToolCallCell; use crate::markdown::append_markdown; +#[cfg(target_os = "windows")] +use crate::onboarding::WSL_INSTRUCTIONS; use crate::render::renderable::ColumnRenderable; use crate::render::renderable::Renderable; use crate::slash_command::SlashCommand; @@ -1787,11 +1789,38 @@ impl ChatWidget { let current_sandbox = self.config.sandbox_policy.clone(); let mut items: Vec = Vec::new(); let presets: Vec = builtin_approval_presets(); + #[cfg(target_os = "windows")] + let header_renderable: Box = if self + .config + .forced_auto_mode_downgraded_on_windows + { + use ratatui_macros::line; + + let mut header = ColumnRenderable::new(); + header.push(line![ + "Codex forced your settings back to Read Only on this Windows machine.".bold() + ]); + header.push(line![ + "To re-enable Auto mode, run Codex inside Windows Subsystem for Linux (WSL) or enable Full Access manually.".dim() + ]); + Box::new(header) + } else { + Box::new(()) + }; + #[cfg(not(target_os = "windows"))] + let header_renderable: Box = Box::new(()); for preset in presets.into_iter() { let is_current = current_approval == preset.approval && current_sandbox == preset.sandbox; let name = preset.label.to_string(); - let description = Some(preset.description.to_string()); + let description_text = preset.description; + let description = if cfg!(target_os = "windows") && preset.id == "auto" { + Some(format!( + "{description_text}\nRequires Windows Subsystem for Linux (WSL). Show installation instructions..." + )) + } else { + Some(description_text.to_string()) + }; let requires_confirmation = preset.id == "full-access" && !self .config @@ -1805,6 +1834,10 @@ impl ChatWidget { preset: preset_clone.clone(), }); })] + } else if cfg!(target_os = "windows") && preset.id == "auto" { + vec![Box::new(|tx| { + tx.send(AppEvent::ShowWindowsAutoModeInstructions); + })] } else { Self::approval_preset_actions(preset.approval, preset.sandbox.clone()) }; @@ -1822,6 +1855,7 @@ impl ChatWidget { title: Some("Select Approval Mode".to_string()), footer_hint: Some(standard_popup_hint_line()), items, + header: header_renderable, ..Default::default() }); } @@ -1909,6 +1943,43 @@ impl ChatWidget { }); } + #[cfg(target_os = "windows")] + pub(crate) fn open_windows_auto_mode_instructions(&mut self) { + use ratatui_macros::line; + + let mut header = ColumnRenderable::new(); + header.push(line![ + "Auto mode requires Windows Subsystem for Linux (WSL2).".bold() + ]); + header.push(line!["Run Codex inside WSL to enable sandboxed commands."]); + header.push(line![""]); + header.push(Paragraph::new(WSL_INSTRUCTIONS).wrap(Wrap { trim: false })); + + let items = vec![SelectionItem { + name: "Back".to_string(), + description: Some( + "Return to the approval mode list. Auto mode stays disabled outside WSL." + .to_string(), + ), + actions: vec![Box::new(|tx| { + tx.send(AppEvent::OpenApprovalsPopup); + })], + dismiss_on_select: true, + ..Default::default() + }]; + + self.bottom_pane.show_selection_view(SelectionViewParams { + title: None, + footer_hint: Some(standard_popup_hint_line()), + items, + header: Box::new(header), + ..Default::default() + }); + } + + #[cfg(not(target_os = "windows"))] + pub(crate) fn open_windows_auto_mode_instructions(&mut self) {} + /// Set the approval policy in the widget's config copy. pub(crate) fn set_approval_policy(&mut self, policy: AskForApproval) { self.config.approval_policy = policy; diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__approvals_selection_popup.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__approvals_selection_popup.snap index 1d612966..190594b1 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__approvals_selection_popup.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__approvals_selection_popup.snap @@ -6,12 +6,12 @@ expression: popup › 1. Read Only (current) Codex can read files and answer questions. Codex requires approval to make edits, run commands, or - access network + access network. 2. Auto Codex can read files, make edits, and run commands in the workspace. Codex requires approval to work - outside the workspace or access network + outside the workspace or access network. 3. Full Access Codex can read files, make edits, and run commands with network access, without approval. Exercise - caution + caution. Press enter to confirm or esc to go back diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__approvals_selection_popup@windows.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__approvals_selection_popup@windows.snap new file mode 100644 index 00000000..7d16ad57 --- /dev/null +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__approvals_selection_popup@windows.snap @@ -0,0 +1,19 @@ +--- +source: tui/src/chatwidget/tests.rs +expression: popup +--- + Select Approval Mode + +› 1. Read Only (current) Codex can read files and answer questions. Codex + requires approval to make edits, run commands, or + access network. + 2. Auto Codex can read files, make edits, and run commands + in the workspace. Codex requires approval to work + outside the workspace or access network. + Requires Windows Subsystem for Linux (WSL). Show + installation instructions... + 3. Full Access Codex can read files, make edits, and run commands + with network access, without approval. Exercise + caution. + + Press enter to confirm or esc to go back diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index a9c1d58c..52664362 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -1276,9 +1276,36 @@ fn approvals_selection_popup_snapshot() { chat.open_approvals_popup(); let popup = render_bottom_popup(&chat, 80); + #[cfg(target_os = "windows")] + insta::with_settings!({ snapshot_suffix => "windows" }, { + assert_snapshot!("approvals_selection_popup", popup); + }); + #[cfg(not(target_os = "windows"))] assert_snapshot!("approvals_selection_popup", popup); } +#[test] +fn approvals_popup_includes_wsl_note_for_auto_mode() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(); + + if cfg!(target_os = "windows") { + chat.config.forced_auto_mode_downgraded_on_windows = true; + } + chat.open_approvals_popup(); + + let popup = render_bottom_popup(&chat, 80); + assert_eq!( + popup.contains("Requires Windows Subsystem for Linux (WSL)"), + cfg!(target_os = "windows"), + "expected auto preset description to mention WSL requirement only on Windows, popup: {popup}" + ); + assert_eq!( + popup.contains("Codex forced your settings back to Read Only on this Windows machine."), + cfg!(target_os = "windows") && chat.config.forced_auto_mode_downgraded_on_windows, + "expected downgrade notice only when auto mode is forced off on Windows, popup: {popup}" + ); +} + #[test] fn full_access_confirmation_popup_snapshot() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(); @@ -1293,6 +1320,20 @@ fn full_access_confirmation_popup_snapshot() { assert_snapshot!("full_access_confirmation_popup", popup); } +#[cfg(target_os = "windows")] +#[test] +fn windows_auto_mode_instructions_popup_lists_install_steps() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(); + + chat.open_windows_auto_mode_instructions(); + + let popup = render_bottom_popup(&chat, 120); + assert!( + popup.contains("wsl --install"), + "expected WSL instructions popup to include install command, popup: {popup}" + ); +} + #[test] fn model_reasoning_selection_popup_snapshot() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(); diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index ea19d51a..542bc464 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -507,14 +507,16 @@ async fn load_config_or_exit( /// or if the current cwd project is already trusted. If not, we need to /// show the trust screen. fn should_show_trust_screen(config: &Config) -> bool { - if config.did_user_set_custom_approval_policy_or_sandbox_mode { - // if the user has overridden either approval policy or sandbox mode, - // skip the trust flow - false - } else { - // otherwise, skip iff the active project is trusted - !config.active_project.is_trusted() + if cfg!(target_os = "windows") { + // Native Windows cannot enforce sandboxed write access without WSL; skip the trust prompt entirely. + return false; } + if config.did_user_set_custom_approval_policy_or_sandbox_mode { + // Respect explicit approval/sandbox overrides made by the user. + return false; + } + // otherwise, skip iff the active project is trusted + !config.active_project.is_trusted() } fn should_show_onboarding( @@ -543,3 +545,38 @@ fn should_show_login_screen(login_status: LoginStatus, config: &Config) -> bool login_status == LoginStatus::NotAuthenticated } + +#[cfg(test)] +mod tests { + use super::*; + use codex_core::config::ConfigOverrides; + use codex_core::config::ConfigToml; + use codex_core::config::ProjectConfig; + use tempfile::TempDir; + + #[test] + fn windows_skips_trust_prompt() -> std::io::Result<()> { + let temp_dir = TempDir::new()?; + let mut config = Config::load_from_base_config_with_overrides( + ConfigToml::default(), + ConfigOverrides::default(), + temp_dir.path().to_path_buf(), + )?; + config.did_user_set_custom_approval_policy_or_sandbox_mode = false; + config.active_project = ProjectConfig { trust_level: None }; + + let should_show = should_show_trust_screen(&config); + if cfg!(target_os = "windows") { + assert!( + !should_show, + "Windows trust prompt should always be skipped on native Windows" + ); + } else { + assert!( + should_show, + "Non-Windows should still show trust prompt when project is untrusted" + ); + } + Ok(()) + } +}