fix(xurl): treat PermissionError from PATH lookup as unavailable (#322)

is_available() only caught FileNotFoundError and TimeoutExpired. On WSL,
a /mnt/c/.../WindowsApps entry on $PATH returns EACCES during exec, and
Python raises PermissionError. That escaped is_available() and crashed
pipeline.diagnose() before any source ran.

Catch OSError instead. It covers FileNotFoundError, PermissionError, and
any other spawn-time OS error, so a non-executable xurl on PATH falls
through to the next backend instead of aborting the run.
This commit is contained in:
Ilia Alshanetsky
2026-04-26 17:16:14 -04:00
committed by GitHub
parent bbf892aecc
commit 5b87cca886
2 changed files with 11 additions and 3 deletions
+4 -3
View File
@@ -46,9 +46,10 @@ def is_available() -> bool:
timeout=10, timeout=10,
) )
return result.returncode == 0 and '"username"' in result.stdout return result.returncode == 0 and '"username"' in result.stdout
except FileNotFoundError: except (OSError, subprocess.TimeoutExpired):
return False # OSError covers FileNotFoundError (no xurl on PATH) and
except subprocess.TimeoutExpired: # PermissionError (a non-executable match on PATH, e.g. WSL's
# /mnt/c/.../WindowsApps shim returning EACCES on exec).
return False return False
+7
View File
@@ -44,6 +44,13 @@ class TestIsAvailable(unittest.TestCase):
with mock.patch("subprocess.run", side_effect=FileNotFoundError): with mock.patch("subprocess.run", side_effect=FileNotFoundError):
self.assertFalse(xurl_x.is_available()) self.assertFalse(xurl_x.is_available())
def test_returns_false_on_permission_error(self):
# WSL hits this when a Windows-mounted PATH entry points at an
# exec-blocked shim (e.g. WindowsApps), which raises PermissionError
# before any other PATH candidate is tried.
with mock.patch("subprocess.run", side_effect=PermissionError(13, "Permission denied", "xurl")):
self.assertFalse(xurl_x.is_available())
def test_returns_false_on_timeout(self): def test_returns_false_on_timeout(self):
import subprocess import subprocess
with mock.patch("subprocess.run", side_effect=subprocess.TimeoutExpired("xurl", 10)): with mock.patch("subprocess.run", side_effect=subprocess.TimeoutExpired("xurl", 10)):