On Python 3.14, stowing into a tree whose source directory has had its execute permission removed reports the wrong error. Expected PermissionDenied, actual ConflictsWithExistingLink.
Cause
pathlib.Path.exists() no longer propagates PermissionError. Same directory, execute permission removed:
python 3.14.4
Path(src/aaa).exists() = False
Path(src/aaa).is_dir() = False
iterdir() raises PermissionError
python 3.13.0
Path(src/aaa).exists() raises PermissionError
AbstractBaseStow._collect_actions calls dest_path.exists() inside a try/except PermissionError and depends on that raise to add a PermissionDenied error. On 3.14 the call returns False, the permission problem goes unnoticed, and collection continues to a misleading conflict error.
Impact
The CI matrix is 3.10-3.13, where the behaviour is correct, so this does not currently show up in CI. It will as soon as 3.14 is added to the matrix.
Two tests cover the affected paths and are marked xfail on 3.14 pointing at this issue:
tests/test_stow.py::test_stow_unfolding_with_first_sources_execute_permission_removed
tests/test_unstow.py::test_unstow_folding_with_multiple_sources_with_execute_permission_unset
Fix direction
Stop inferring permission failures from exists(). Check access explicitly (utils.is_directory_executable already exists) or move the detection to the point where the directory is actually read, which still raises.
On Python 3.14, stowing into a tree whose source directory has had its execute permission removed reports the wrong error. Expected
PermissionDenied, actualConflictsWithExistingLink.Cause
pathlib.Path.exists()no longer propagatesPermissionError. Same directory, execute permission removed:AbstractBaseStow._collect_actionscallsdest_path.exists()inside atry/except PermissionErrorand depends on that raise to add aPermissionDeniederror. On 3.14 the call returnsFalse, the permission problem goes unnoticed, and collection continues to a misleading conflict error.Impact
The CI matrix is 3.10-3.13, where the behaviour is correct, so this does not currently show up in CI. It will as soon as 3.14 is added to the matrix.
Two tests cover the affected paths and are marked
xfailon 3.14 pointing at this issue:tests/test_stow.py::test_stow_unfolding_with_first_sources_execute_permission_removedtests/test_unstow.py::test_unstow_folding_with_multiple_sources_with_execute_permission_unsetFix direction
Stop inferring permission failures from
exists(). Check access explicitly (utils.is_directory_executablealready exists) or move the detection to the point where the directory is actually read, which still raises.