From 0fdb9f9cf394f86f41996c555767c00c17ff0392 Mon Sep 17 00:00:00 2001 From: David Wiesen Date: Mon, 20 Apr 2026 05:04:15 -0700 Subject: [PATCH] Retry Windows sandbox runner logon failures --- .../windows-sandbox-rs/src/elevated_impl.rs | 111 ++++++++++++++---- 1 file changed, 86 insertions(+), 25 deletions(-) diff --git a/codex-rs/windows-sandbox-rs/src/elevated_impl.rs b/codex-rs/windows-sandbox-rs/src/elevated_impl.rs index 327425bd07..aab99eb079 100644 --- a/codex-rs/windows-sandbox-rs/src/elevated_impl.rs +++ b/codex-rs/windows-sandbox-rs/src/elevated_impl.rs @@ -40,6 +40,9 @@ mod windows_impl { use crate::logging::log_success; use crate::policy::SandboxPolicy; use crate::policy::parse_policy; + use crate::setup::SandboxSetupRequest; + use crate::setup::SetupRootOverrides; + use crate::setup::run_elevated_setup; use crate::token::convert_string_sid_to_sid; use crate::winutil::quote_windows_arg; use crate::winutil::resolve_sid; @@ -222,6 +225,13 @@ mod windows_impl { } } + const ERROR_LOGON_FAILURE: i32 = 1326; + const ERROR_ACCOUNT_LOCKED_OUT: i32 = 1909; + + fn should_retry_after_logon_failure(err: i32) -> bool { + matches!(err, ERROR_LOGON_FAILURE | ERROR_ACCOUNT_LOCKED_OUT) + } + /// Launches the command runner under the sandbox user and captures its output. #[allow(clippy::too_many_arguments)] pub fn run_windows_sandbox_capture( @@ -252,7 +262,7 @@ mod windows_impl { let logs_base_dir: Option<&Path> = Some(sandbox_base.as_path()); log_start(&command, logs_base_dir); - let sandbox_creds = require_logon_sandbox_creds( + let mut sandbox_creds = require_logon_sandbox_creds( &policy, sandbox_policy_cwd, cwd, @@ -328,9 +338,9 @@ mod windows_impl { let mut si: STARTUPINFOW = unsafe { std::mem::zeroed() }; si.cb = std::mem::size_of::() as u32; let mut pi: PROCESS_INFORMATION = unsafe { std::mem::zeroed() }; - let user_w = to_wide(&sandbox_creds.username); + let mut user_w = to_wide(&sandbox_creds.username); let domain_w = to_wide("."); - let password_w = to_wide(&sandbox_creds.password); + let mut password_w = to_wide(&sandbox_creds.password); // Suppress WER/UI popups from the runner process so we can collect exit codes. let _ = unsafe { SetErrorMode(0x0001 | 0x0002) }; // SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX @@ -345,36 +355,80 @@ mod windows_impl { ); // Ensure command line buffer is mutable and includes the exe as argv[0]. - let spawn_res = unsafe { - CreateProcessWithLogonW( - user_w.as_ptr(), - domain_w.as_ptr(), - password_w.as_ptr(), - LOGON_WITH_PROFILE, - exe_w.as_ptr(), - cmdline_vec.as_mut_ptr(), - windows_sys::Win32::System::Threading::CREATE_NO_WINDOW - | windows_sys::Win32::System::Threading::CREATE_UNICODE_ENVIRONMENT, - env_block - .as_ref() - .map(|b| b.as_ptr() as *const c_void) - .unwrap_or(ptr::null()), - cwd_w.as_ptr(), - &si, - &mut pi, - ) - }; - if spawn_res == 0 { + let mut launch_attempt = 1; + loop { + let spawn_res = unsafe { + CreateProcessWithLogonW( + user_w.as_ptr(), + domain_w.as_ptr(), + password_w.as_ptr(), + LOGON_WITH_PROFILE, + exe_w.as_ptr(), + cmdline_vec.as_mut_ptr(), + windows_sys::Win32::System::Threading::CREATE_NO_WINDOW + | windows_sys::Win32::System::Threading::CREATE_UNICODE_ENVIRONMENT, + env_block + .as_ref() + .map(|b| b.as_ptr() as *const c_void) + .unwrap_or(ptr::null()), + cwd_w.as_ptr(), + &si, + &mut pi, + ) + }; + if spawn_res != 0 { + break; + } + let err = unsafe { GetLastError() } as i32; log_note( &format!( - "runner launch failed before process start: exe={} cmdline={} error={err}", + "runner launch failed before process start: exe={} cmdline={} error={err} attempt={launch_attempt}", runner_exe.display(), runner_full_cmd ), logs_base_dir, ); - return Err(anyhow::anyhow!("CreateProcessWithLogonW failed: {err}")); + if launch_attempt > 1 || !should_retry_after_logon_failure(err) { + return Err(anyhow::anyhow!("CreateProcessWithLogonW failed: {err}")); + } + + log_note( + &format!( + "runner launch hit recoverable logon error {err}; reprovisioning sandbox users and retrying" + ), + logs_base_dir, + ); + run_elevated_setup( + SandboxSetupRequest { + policy: &policy, + policy_cwd: sandbox_policy_cwd, + command_cwd: cwd, + env_map: &env_map, + codex_home, + proxy_enforced, + }, + SetupRootOverrides { + read_roots: read_roots_override.map(<[PathBuf]>::to_vec), + write_roots: write_roots_override.map(<[PathBuf]>::to_vec), + deny_write_paths: Some(deny_write_paths_override.to_vec()), + }, + )?; + sandbox_creds = require_logon_sandbox_creds( + &policy, + sandbox_policy_cwd, + cwd, + &env_map, + codex_home, + read_roots_override, + write_roots_override, + deny_write_paths_override, + proxy_enforced, + )?; + user_w = to_wide(&sandbox_creds.username); + password_w = to_wide(&sandbox_creds.password); + cmdline_vec = to_wide(&runner_full_cmd); + launch_attempt += 1; } if let Err(err) = connect_pipe(h_pipe_in) { @@ -511,6 +565,13 @@ mod windows_impl { fn applies_network_block_for_read_only() { assert!(!SandboxPolicy::new_read_only_policy().has_full_network_access()); } + + #[test] + fn retries_only_known_logon_errors() { + assert!(should_retry_after_logon_failure(1326)); + assert!(should_retry_after_logon_failure(1909)); + assert!(!should_retry_after_logon_failure(5)); + } } }