diff --git a/crates/uv/src/commands/python/install.rs b/crates/uv/src/commands/python/install.rs index 9905903de8e9..4051d4a1daf1 100644 --- a/crates/uv/src/commands/python/install.rs +++ b/crates/uv/src/commands/python/install.rs @@ -354,32 +354,53 @@ pub(crate) async fn install( target.simplified_display() ); - // Figure out what installation it references, if any - let existing = find_matching_bin_link( - installations - .iter() - .copied() - .chain(existing_installations.iter()), - &target, - ); + // Check if the existing link is valid + let valid_link = match target.read_link().and_then(|target| target.try_exists()) + { + Ok(valid) => valid, + Err(err) => { + debug!("Failed to inspect executable with error: {err}"); + true // Treat this as a valid link, we won't remove it without `--force` + } + }; + + // Figure out what installation it references, if anyf + let existing = valid_link + .then(|| { + find_matching_bin_link( + installations + .iter() + .copied() + .chain(existing_installations.iter()), + &target, + ) + }) + .flatten(); match existing { None => { // There's an existing executable we don't manage, require `--force` - if !force { - errors.push(( - installation.key(), - anyhow::anyhow!( - "Executable already exists at `{}` but is not managed by uv; use `--force` to replace it", - to.simplified_display() - ), - )); - continue; + if valid_link { + if !force { + errors.push(( + installation.key(), + anyhow::anyhow!( + "Executable already exists at `{}` but is not managed by uv; use `--force` to replace it", + to.simplified_display() + ), + )); + continue; + } + debug!( + "Replacing existing executable at `{}` due to `--force`", + target.simplified_display() + ); + } else { + debug!( + "Replacing existing executable at `{}` due to an invalid symlink", + target.simplified_display() + ); } - debug!( - "Replacing existing executable at `{}` due to `--force`", - target.simplified_display() - ); } Some(existing) if existing == *installation => { // The existing link points to the same installation, so we're done unless