fix(skills): make _rmtree_writable handle read-only directories, not just files

The cherry-picked fix's onerror handler chmod'd only the failing path, but
unlinking a child requires write permission on its PARENT directory. On a true
Nix-store copy (r-xr-xr-x dirs + files) rmtree still failed. Now chmod the
parent dir as well before retrying.

Also rewrites the regression test: the original asserted the helper FAILS on a
read-only dir (documenting the limitation), which is the wrong success criterion.
Split into two tests — restore succeeds on a full read-only tree (real Nix case),
and manifest is preserved when removal genuinely cannot proceed (monkeypatched).
This commit is contained in:
teknium1
2026-05-30 02:05:10 -07:00
committed by Teknium
parent 83a7d0b601
commit 8ae0802d59
2 changed files with 80 additions and 16 deletions
+14 -5
View File
@@ -567,15 +567,24 @@ def sync_skills(quiet: bool = False) -> dict:
def _rmtree_writable(path: Path) -> None:
"""Remove a directory tree, making read-only files writable first.
"""Remove a directory tree, making read-only entries writable first.
Handles immutable package sources (Nix store, deb/rpm installs) that
preserve read-only permissions on copied files. See #34860, #34972.
preserve read-only permissions on copied files *and* directories
(``r-xr-xr-x``). Removing a child requires write permission on its
parent directory, so the retry handler makes the failing path **and its
parent** writable before re-attempting. See #34860, #34972.
"""
import stat
def _on_error(func, fpath, exc_info):
# Make the file/directory writable and retry
import stat
os.chmod(fpath, stat.S_IWRITE)
# Unlinking a child requires the parent dir to be writable, so chmod
# the parent as well as the failing path, then retry.
for target in (os.path.dirname(fpath), fpath):
try:
os.chmod(target, stat.S_IRWXU)
except OSError:
pass
func(fpath)
shutil.rmtree(path, onerror=_on_error)