fix(skills): fix transaction ordering in reset_bundled_skill and handle read-only files in rmtree#35131
Conversation
…le read-only files in rmtree Two related bugs in tools/skills_sync.py affecting Nix-store and immutable-package installs: **NousResearch#34972 — reset_bundled_skill corrupts manifest on rmtree failure:** The function deleted the manifest entry BEFORE attempting rmtree. If rmtree failed (read-only files from Nix store), the function returned early — leaving the skill in a manifest-less limbo state where future syncs silently skip it forever. Fix: reorder steps — attempt rmtree FIRST, only delete manifest entry after rmtree succeeds. If rmtree fails, nothing is changed. **NousResearch#34860 — stale .bak directories after sync:** sync_skills() called shutil.rmtree(backup, ignore_errors=True) which silently failed on read-only files, leaving persistent .bak dirs. Fix: add _rmtree_writable() helper that makes files writable via an onerror callback before retrying removal. Used in both sync_skills() backup cleanup and reset_bundled_skill(). Fixes NousResearch#34972 Fixes NousResearch#34860
Hermes Agent Review NoteI checked this PR on Paul’s Windows host because the change touches read-only/removal behavior. The new regression test currently fails on native Windows/MSYS: Why: Suggested fix: simulate the deletion failure deterministically by monkeypatching Evidence worktree: |
Problem
Two related bugs in
tools/skills_sync.pyaffecting installs with read-only files (Nix store, deb/rpm packaging).#34972 —
reset_bundled_skillcorrupts manifest on rmtree failurehermes skills reset <name> --restore --yesdeletes the manifest entry before attemptingshutil.rmtree(). If rmtree fails (e.g. read-only files from Nix store), the function returns early — but the manifest entry is already gone. The skill enters a permanent limbo state:sync_skills()seesskill_name not in manifest && dest exists && dir_hash != bundled_hash→ silently skips#34860 — Stale
.bakdirectories after syncsync_skills()callsshutil.rmtree(backup, ignore_errors=True)which silently fails on read-only files, leaving persistent.bakdirectories under~/.hermes/skills/.Fix
Transaction reordering (#34972)
Reorder
reset_bundled_skill()— attempt rmtree first, only delete manifest entry after rmtree succeeds. If rmtree fails, nothing is changed:_rmtree_writablehelper (#34860)Add
_rmtree_writable(path)that usesshutil.rmtree(onerror=...)to make read-only files writable before retrying removal. Used in both:sync_skills()backup cleanup (replacesignore_errors=True)reset_bundled_skill()user copy deletionTests
test_reset_restore_preserves_manifest_on_rmtree_failure— verifies manifest is preserved when rmtree failsFixes #34972
Fixes #34860