Add WSL escape hatch for Windows sandbox NUX

This commit is contained in:
David Wiesen
2026-05-21 09:52:03 -07:00
parent 0035d7bd18
commit 9274005ba7
5 changed files with 67 additions and 0 deletions

View File

@@ -1139,6 +1139,27 @@ impl App {
let _ = (preset, mode);
}
}
AppEvent::AcknowledgeWindowsSandboxSetup => {
#[cfg(target_os = "windows")]
{
match ConfigEditsBuilder::new(&self.config.codex_home)
.set_windows_wsl_setup_acknowledged(true)
.apply()
.await
{
Ok(()) => {
self.config.windows_wsl_setup_acknowledged = true;
self.chat_widget.set_windows_wsl_setup_acknowledged(true);
}
Err(err) => {
tracing::warn!(
error = %err,
"failed to persist Windows sandbox onboarding acknowledgement"
);
}
}
}
}
AppEvent::PersistModelSelection { model, effort } => {
let profile = self.active_profile.as_deref();
match ConfigEditsBuilder::new(&self.config.codex_home)

View File

@@ -664,6 +664,10 @@ pub(crate) enum AppEvent {
mode: WindowsSandboxEnableMode,
},
/// Record that the Windows sandbox onboarding has been acknowledged.
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
AcknowledgeWindowsSandboxSetup,
/// Update the Windows sandbox feature mode without changing approval presets.
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]

View File

@@ -8826,6 +8826,7 @@ impl ChatWidget {
let accept_otel = self.session_telemetry.clone();
let legacy_otel = self.session_telemetry.clone();
let legacy_preset = preset.clone();
let skip_otel = self.session_telemetry.clone();
let quit_otel = self.session_telemetry.clone();
let items = vec![
SelectionItem {
@@ -8860,6 +8861,20 @@ impl ChatWidget {
dismiss_on_select: true,
..Default::default()
},
SelectionItem {
name: "Continue without sandbox (WSL2 / don't warn again)".to_string(),
description: None,
actions: vec![Box::new(move |tx| {
skip_otel.counter(
"codex.windows_sandbox.elevated_prompt_skip",
/*inc*/ 1,
&[],
);
tx.send(AppEvent::AcknowledgeWindowsSandboxSetup);
})],
dismiss_on_select: true,
..Default::default()
},
SelectionItem {
name: "Quit".to_string(),
description: None,
@@ -8982,6 +8997,7 @@ impl ChatWidget {
#[cfg(target_os = "windows")]
pub(crate) fn maybe_prompt_windows_sandbox_enable(&mut self, show_now: bool) {
if show_now
&& !self.config.windows_wsl_setup_acknowledged
&& WindowsSandboxLevel::from_config(&self.config) == WindowsSandboxLevel::Disabled
&& let Some(preset) = builtin_approval_presets()
.into_iter()
@@ -9063,6 +9079,11 @@ impl ChatWidget {
);
}
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
pub(crate) fn set_windows_wsl_setup_acknowledged(&mut self, acknowledged: bool) {
self.config.windows_wsl_setup_acknowledged = acknowledged;
}
#[cfg_attr(not(target_os = "windows"), allow(dead_code))]
pub(crate) fn set_feature_enabled(&mut self, feature: Feature, enabled: bool) -> bool {
if let Err(err) = self.config.features.set_enabled(feature, enabled) {

View File

@@ -220,6 +220,10 @@ async fn startup_prompts_for_windows_sandbox_when_agent_requested() {
popup.contains("Use non-admin sandbox"),
"expected startup prompt to offer non-admin fallback: {popup}"
);
assert!(
popup.contains("Continue without sandbox"),
"expected startup prompt to offer WSL/skip fallback: {popup}"
);
assert!(
popup.contains("Quit"),
"expected startup prompt to offer quit action: {popup}"
@@ -241,6 +245,22 @@ async fn startup_does_not_prompt_for_windows_sandbox_when_not_requested() {
);
}
#[cfg(target_os = "windows")]
#[tokio::test]
async fn startup_does_not_prompt_for_windows_sandbox_after_acknowledgement() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.set_feature_enabled(Feature::WindowsSandbox, /*enabled*/ false);
chat.set_feature_enabled(Feature::WindowsSandboxElevated, /*enabled*/ false);
chat.set_windows_wsl_setup_acknowledged(true);
chat.maybe_prompt_windows_sandbox_enable(/*show_now*/ true);
assert!(
chat.bottom_pane.no_modal_or_popup_active(),
"expected no startup sandbox NUX popup after acknowledgement"
);
}
#[tokio::test]
async fn approvals_popup_shows_disabled_presets() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;

View File

@@ -1415,6 +1415,7 @@ async fn run_ratatui_app(
let should_show_trust_screen = should_show_trust_screen(&config);
let should_prompt_windows_sandbox_nux_at_startup = cfg!(target_os = "windows")
&& trust_decision_was_made
&& !config.windows_wsl_setup_acknowledged
&& WindowsSandboxLevel::from_config(&config) == WindowsSandboxLevel::Disabled;
let Cli {