diff --git a/codex-rs/core/src/exec.rs b/codex-rs/core/src/exec.rs index 37f6c2de96..e0d65c8305 100644 --- a/codex-rs/core/src/exec.rs +++ b/codex-rs/core/src/exec.rs @@ -232,6 +232,72 @@ pub(crate) async fn execute_exec_env( finalize_exec_result(raw_output_result, sandbox, duration) } +#[cfg(target_os = "windows")] +fn extract_create_process_as_user_error_code(err: &str) -> Option { + let marker = "CreateProcessAsUserW failed: "; + let start = err.find(marker)? + marker.len(); + let tail = &err[start..]; + let digits: String = tail.chars().take_while(char::is_ascii_digit).collect(); + if digits.is_empty() { + None + } else { + Some(digits) + } +} + +#[cfg(target_os = "windows")] +fn windowsapps_path_kind(path: &str) -> &'static str { + let lower = path.to_ascii_lowercase(); + if lower.contains("\\program files\\windowsapps\\") { + return "windowsapps_package"; + } + if lower.contains("\\appdata\\local\\microsoft\\windowsapps\\") { + return "windowsapps_alias"; + } + if lower.contains("\\windowsapps\\") { + return "windowsapps_other"; + } + "other" +} + +#[cfg(target_os = "windows")] +fn record_windows_sandbox_spawn_failure( + command_path: Option<&str>, + windows_sandbox_level: codex_protocol::config_types::WindowsSandboxLevel, + err: &str, +) { + let Some(error_code) = extract_create_process_as_user_error_code(err) else { + return; + }; + let path = command_path.unwrap_or("unknown"); + let exe = Path::new(path) + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or("unknown") + .to_ascii_lowercase(); + let path_kind = windowsapps_path_kind(path); + let level = if matches!( + windows_sandbox_level, + codex_protocol::config_types::WindowsSandboxLevel::Elevated + ) { + "elevated" + } else { + "legacy" + }; + if let Some(metrics) = codex_otel::metrics::global() { + let _ = metrics.counter( + "codex.windows_sandbox.createprocessasuserw_failed", + 1, + &[ + ("error_code", error_code.as_str()), + ("path_kind", path_kind), + ("exe", exe.as_str()), + ("level", level), + ], + ); + } +} + #[cfg(target_os = "windows")] async fn exec_windows_sandbox( params: ExecParams, @@ -265,7 +331,9 @@ async fn exec_windows_sandbox( "windows sandbox: failed to resolve codex_home: {err}" ))) })?; - let use_elevated = matches!(windows_sandbox_level, WindowsSandboxLevel::Elevated); + let command_path = command.first().cloned(); + let sandbox_level = windows_sandbox_level; + let use_elevated = matches!(sandbox_level, WindowsSandboxLevel::Elevated); let spawn_res = tokio::task::spawn_blocking(move || { if use_elevated { run_windows_sandbox_capture_elevated( @@ -294,6 +362,11 @@ async fn exec_windows_sandbox( let capture = match spawn_res { Ok(Ok(v)) => v, Ok(Err(err)) => { + record_windows_sandbox_spawn_failure( + command_path.as_deref(), + sandbox_level, + &err.to_string(), + ); return Err(CodexErr::Io(io::Error::other(format!( "windows sandbox: {err}" ))));