use anyhow::Context;use anyhow::Result;use codex_utils_string::sanitize_metric_tag_value;use serde::Deserialize;use serde::Serialize;use std::fs;use std::io::ErrorKind;use std::path::Path;use std::path::PathBuf;/// These represent the most common failures for the elevated sandbox setup.////// Codes are used as metric tags.#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]#[serde(rename_all = "snake_case")]pub enum SetupErrorCode { // Orchestrator (run in CLI) failures. /// Failed to create `codex_home/.sandbox` in the orchestrator. OrchestratorSandboxDirCreateFailed, /// Failed to determine whether the current process is elevated. OrchestratorElevationCheckFailed, /// The setup command requires an already elevated process. OrchestratorElevationRequired, /// Failed to serialize the elevation payload before launching the helper. OrchestratorPayloadSerializeFailed, /// Failed to launch the setup helper process (spawn or ShellExecuteExW). OrchestratorHelperLaunchFailed, /// User canceled the UAC prompt while launching the helper. OrchestratorHelperLaunchCanceled, /// Helper exited non-zero and no structured report was available. OrchestratorHelperExitNonzero, /// Helper exited non-zero and reading `setup_error.json` failed. OrchestratorHelperReportReadFailed, /// Helper exited successfully before setup completed. OrchestratorHelperIncomplete, // Helper (elevated process) failures. /// Helper failed while validating or decoding the request payload. HelperRequestArgsFailed, /// Helper failed to create `codex_home/.sandbox`. HelperSandboxDirCreateFailed, /// Helper failed to open or write the setup log. HelperLogFailed, /// Helper failed in the provisioning phase (fallback bucket). HelperUserProvisionFailed, /// Helper failed to create the sandbox users local group. HelperUsersGroupCreateFailed, /// Helper failed to create or update a sandbox user account. HelperUserCreateOrUpdateFailed, /// Helper failed to protect user passwords with DPAPI. HelperDpapiProtectFailed, /// Helper failed to write the sandbox users secrets file. HelperUsersFileWriteFailed, /// Helper failed to write or protect the setup marker file. HelperSetupMarkerWriteFailed, /// Helper failed to resolve a SID or convert it to a PSID. HelperSidResolveFailed, /// Helper failed to load or convert capability SIDs. HelperCapabilitySidFailed, /// Helper failed to initialize COM for firewall configuration. HelperFirewallComInitFailed, /// Helper failed to access firewall policy or rule collections. HelperFirewallPolicyAccessFailed, /// Helper detected that local firewall policy changes will not fully take effect. HelperFirewallPolicyIneffective, /// Helper failed to create, update, or add the firewall rule. HelperFirewallRuleCreateOrAddFailed, /// Helper failed to verify the configured firewall rule scope. HelperFirewallRuleVerifyFailed, /// Helper failed to spawn the read-ACL helper process. HelperReadAclHelperSpawnFailed, /// Helper failed to lock down sandbox directories via ACLs. HelperSandboxLockFailed, /// Helper failed for an unmapped or unexpected reason. HelperUnknownError,}impl SetupErrorCode { pub fn as_str(self) -> &'static str { match self { Self::OrchestratorSandboxDirCreateFailed => "orchestrator_sandbox_dir_create_failed", Self::OrchestratorElevationCheckFailed => "orchestrator_elevation_check_failed", Self::OrchestratorElevationRequired => "orchestrator_elevation_required", Self::OrchestratorPayloadSerializeFailed => "orchestrator_payload_serialize_failed", Self::OrchestratorHelperLaunchFailed => "orchestrator_helper_launch_failed", Self::OrchestratorHelperLaunchCanceled => "orchestrator_helper_launch_canceled", Self::OrchestratorHelperExitNonzero => "orchestrator_helper_exit_nonzero", Self::OrchestratorHelperReportReadFailed => "orchestrator_helper_report_read_failed", Self::OrchestratorHelperIncomplete => "orchestrator_helper_incomplete", Self::HelperRequestArgsFailed => "helper_request_args_failed", Self::HelperSandboxDirCreateFailed => "helper_sandbox_dir_create_failed", Self::HelperLogFailed => "helper_log_failed", Self::HelperUserProvisionFailed => "helper_user_provision_failed", Self::HelperUsersGroupCreateFailed => "helper_users_group_create_failed", Self::HelperUserCreateOrUpdateFailed => "helper_user_create_or_update_failed", Self::HelperDpapiProtectFailed => "helper_dpapi_protect_failed", Self::HelperUsersFileWriteFailed => "helper_users_file_write_failed", Self::HelperSetupMarkerWriteFailed => "helper_setup_marker_write_failed", Self::HelperSidResolveFailed => "helper_sid_resolve_failed", Self::HelperCapabilitySidFailed => "helper_capability_sid_failed", Self::HelperFirewallComInitFailed => "helper_firewall_com_init_failed", Self::HelperFirewallPolicyAccessFailed => "helper_firewall_policy_access_failed", Self::HelperFirewallPolicyIneffective => "helper_firewall_policy_ineffective", Self::HelperFirewallRuleCreateOrAddFailed => { "helper_firewall_rule_create_or_add_failed" } Self::HelperFirewallRuleVerifyFailed => "helper_firewall_rule_verify_failed", Self::HelperReadAclHelperSpawnFailed => "helper_read_acl_helper_spawn_failed", Self::HelperSandboxLockFailed => "helper_sandbox_lock_failed", Self::HelperUnknownError => "helper_unknown_error", } }}#[derive(Debug, Clone, Serialize, Deserialize)]pub struct SetupErrorReport { pub code: SetupErrorCode, pub message: String,}#[derive(Debug, PartialEq, Eq)]pub struct SetupFailure { pub code: SetupErrorCode, pub message: String,}impl SetupFailure { pub fn new(code: SetupErrorCode, message: impl Into<String>) -> Self { Self { code, message: message.into(), } } pub fn from_report(report: SetupErrorReport) -> Self { Self::new(report.code, report.message) } pub fn metric_message(&self) -> String { sanitize_setup_metric_tag_value(&self.message) }}impl std::fmt::Display for SetupFailure { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}: {}", self.code.as_str(), self.message) }}impl std::error::Error for SetupFailure {}pub fn failure(code: SetupErrorCode, message: impl Into<String>) -> anyhow::Error { anyhow::Error::new(SetupFailure::new(code, message))}pub fn extract_failure(err: &anyhow::Error) -> Option<&SetupFailure> { err.downcast_ref::<SetupFailure>()}pub fn setup_error_path(codex_home: &Path) -> PathBuf { codex_home.join(".sandbox").join("setup_error.json")}pub fn clear_setup_error_report(codex_home: &Path) -> Result<()> { let path = setup_error_path(codex_home); match fs::remove_file(&path) { Ok(()) => Ok(()), Err(err) if err.kind() == ErrorKind::NotFound => Ok(()), Err(err) => Err(err).with_context(|| format!("remove {}", path.display())), }}pub fn write_setup_error_report(codex_home: &Path, report: &SetupErrorReport) -> Result<()> { let sandbox_dir = codex_home.join(".sandbox"); fs::create_dir_all(&sandbox_dir) .with_context(|| format!("create sandbox dir {}", sandbox_dir.display()))?; let path = setup_error_path(codex_home); let json = serde_json::to_vec_pretty(report)?; fs::write(&path, json).with_context(|| format!("write {}", path.display()))?; Ok(())}pub fn read_setup_error_report(codex_home: &Path) -> Result<Option<SetupErrorReport>> { let path = setup_error_path(codex_home); let bytes = match fs::read(&path) { Ok(bytes) => bytes, Err(err) if err.kind() == ErrorKind::NotFound => return Ok(None), Err(err) => return Err(err).with_context(|| format!("read {}", path.display())), }; let report = serde_json::from_slice::<SetupErrorReport>(&bytes) .with_context(|| format!("parse {}", path.display()))?; Ok(Some(report))}/// Sanitize a setup error message for use as a metric tag.pub fn sanitize_setup_metric_tag_value(value: &str) -> String { sanitize_metric_tag_value(redact_home_paths(value).as_str())}fn redact_home_paths(value: &str) -> String { let mut usernames: Vec<String> = Vec::new(); if let Ok(username) = std::env::var("USERNAME") && !username.trim().is_empty() { usernames.push(username); } if let Ok(user) = std::env::var("USER") && !user.trim().is_empty() && !usernames.iter().any(|v| v.eq_ignore_ascii_case(&user)) { usernames.push(user); } redact_username_segments(value, &usernames)}fn redact_username_segments(value: &str, usernames: &[String]) -> String { if usernames.is_empty() { return value.to_string(); } let mut segments: Vec<String> = Vec::new(); let mut separators: Vec<char> = Vec::new(); let mut current = String::new(); for ch in value.chars() { if ch == '\\' || ch == '/' { segments.push(std::mem::take(&mut current)); separators.push(ch); } else { current.push(ch); } } segments.push(current); for segment in &mut segments { let matches = if cfg!(windows) { usernames .iter() .any(|name| segment.eq_ignore_ascii_case(name)) } else { usernames.iter().any(|name| segment == name) }; if matches { *segment = "<user>".to_string(); } } let mut out = String::new(); for (idx, segment) in segments.iter().enumerate() { out.push_str(segment); if let Some(sep) = separators.get(idx) { out.push(*sep); } } out}#[cfg(test)]mod tests { use super::redact_username_segments; use pretty_assertions::assert_eq; #[test] fn sanitize_tag_value_redacts_username_segments() { let usernames = vec!["Alice".to_string(), "Bob".to_string()]; let msg = "failed to write C:\\Users\\Alice\\file.txt; fallback D:\\Profiles\\Bob\\x"; let redacted = redact_username_segments(msg, &usernames); assert_eq!( redacted, "failed to write C:\\Users\\<user>\\file.txt; fallback D:\\Profiles\\<user>\\x" ); } #[test] fn sanitize_tag_value_leaves_unknown_segments() { let usernames = vec!["Alice".to_string()]; let msg = "failed to write E:\\data\\file.txt"; let redacted = redact_username_segments(msg, &usernames); assert_eq!(redacted, msg); } #[test] fn sanitize_tag_value_redacts_multiple_occurrences() { let usernames = vec!["Alice".to_string()]; let msg = "C:\\Users\\Alice\\a and C:\\Users\\Alice\\b"; let redacted = redact_username_segments(msg, &usernames); assert_eq!(redacted, "C:\\Users\\<user>\\a and C:\\Users\\<user>\\b"); }}