diff --git a/crates/uv-python/src/discovery.rs b/crates/uv-python/src/discovery.rs index 67f8f37ff..c067082dd 100644 --- a/crates/uv-python/src/discovery.rs +++ b/crates/uv-python/src/discovery.rs @@ -884,6 +884,14 @@ impl Error { ); false } + #[cfg(windows)] + InterpreterError::CorruptWindowsPackage { path, err } => { + debug!( + "Skipping bad interpreter at {} from {source}: {err}", + path.display() + ); + false + } InterpreterError::NotFound(path) | InterpreterError::BrokenSymlink(BrokenSymlink { path, .. }) => { // If the interpreter is from an active, valid virtual environment, we should diff --git a/crates/uv-python/src/interpreter.rs b/crates/uv-python/src/interpreter.rs index 0f074ebb6..fc5adb833 100644 --- a/crates/uv-python/src/interpreter.rs +++ b/crates/uv-python/src/interpreter.rs @@ -34,6 +34,9 @@ use crate::{ VirtualEnvironment, }; +#[cfg(windows)] +use windows_sys::Win32::Foundation::{APPMODEL_ERROR_NO_PACKAGE, ERROR_CANT_ACCESS_FILE}; + /// A Python executable and its associated platform markers. #[derive(Debug, Clone)] pub struct Interpreter { @@ -760,6 +763,13 @@ pub enum Error { #[source] err: io::Error, }, + #[cfg(windows)] + #[error("Failed to query Python interpreter at `{path}`")] + CorruptWindowsPackage { + path: PathBuf, + #[source] + err: io::Error, + }, #[error("{0}")] UnexpectedResponse(UnexpectedResponseError), #[error("{0}")] @@ -872,10 +882,23 @@ impl InterpreterInfo { .arg("-c") .arg(script) .output() - .map_err(|err| Error::SpawnFailed { - path: interpreter.to_path_buf(), - err, - })?; + .map_err( + |err| match err.raw_os_error().and_then(|code| u32::try_from(code).ok()) { + // These error codes are returned if the Python interpreter is a corrupt MSIX + // package, which we want to differentiate from a typical spawn failure. + #[cfg(windows)] + Some(APPMODEL_ERROR_NO_PACKAGE | ERROR_CANT_ACCESS_FILE) => { + Error::CorruptWindowsPackage { + path: interpreter.to_path_buf(), + err, + } + } + _ => Error::SpawnFailed { + path: interpreter.to_path_buf(), + err, + }, + }, + )?; if !output.status.success() { let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();