Bug Description
When a user modifies a bundled skill (e.g. edits the SKILL.md in ~/.hermes/skills/), sync_skills() detects the hash mismatch and correctly marks it as user-modified, skipping future updates. However, hermes skills reset <name> (without --restore) claims to resolve this but actually does nothing for truly modified skills.
Two misleading messages are involved:
-
reset_bundled_skill() returns: "Future hermes update runs will re-baseline against your current copy and accept upstream changes." — This is false when the skill was actually modified. The next sync_skills() call hits the not_in_manifest + dest.exists() + dir_hash(dest) != bundled_hash branch, which skips the skill without writing a manifest entry. The skill remains permanently skipped for future updates.
-
sync_skills() prints: "yours was kept. Run hermes skills reset {name} to replace it with the bundled version." — But reset without --restore does not replace it. The user gets a circular suggestion: run reset → it says "yours was kept, run reset."
Steps to Reproduce
- Start Hermes. A bundled skill
foo is synced to ~/.hermes/skills/foo/.
- Edit
~/.hermes/skills/foo/SKILL.md so the user hash diverges from the origin hash.
- Next launch:
~ foo (user-modified, skipping) is printed.
- Run
hermes skills reset foo (no --restore). Output says "Cleared manifest entry for 'foo'. Future hermes update runs will re-baseline..."
- Next launch:
~ foo (user-modified, skipping) again. Nothing changed.
Expected Behavior
hermes skills reset <name> without --restore should either:
- (a) Actually unblock the skill so future bundled updates can come in (write the current bundled hash into the manifest so the next sync can detect bundled-vs-user divergence properly), or
- (b) Honestly tell the user "Your local copy is preserved and will never receive bundled updates. Use
--restore to revert to the shipped version."
Actual Behavior
The command returns a success message that is demonstrably false for user-modified skills. The skill remains permanently stuck in "user-modified" limbo, and the command output implies a resolution that never happens.
Affected Component
Skills (skill loading, skill hub, skill guard)
Root Cause Analysis
Two locations in tools/skills_sync.py:
1. Lines 409-411 — reset_bundled_skill() non---restore path:
action = "manifest_cleared"
message = (
f"Cleared manifest entry for '{name}'. Future `hermes update` runs "
f"will re-baseline against your current copy and accept upstream changes."
)
This is incorrect when the skill was modified. Clearing the manifest causes sync_skills() to treat it as a new skill conflict (lines 204-226), which skips the skill without writing a manifest entry when _dir_hash(dest) != bundled_hash. The next sync repeats the same loop.
2. Lines 222-225 — sync_skills() new-skill-conflict path:
print(
f" ⚠ {skill_name}: bundled version shipped but you "
f"already have a local skill by this name — yours "
f"was kept. Run `hermes skills reset {skill_name}` "
f"to replace it with the bundled version."
)
Suggesting reset is circular—reset without --restore cannot replace anything for modified skills. The correct suggestion should be reset --restore.
Proposed Fix
Option A (honest messaging, minimal fix): Fix the two messages to be accurate about what happens. The reset_bundled_skill() message should say something like: "Cleared manifest entry for '{name}'. If you modified this skill, your local copy is preserved and will not receive upstream updates. Use --restore to revert to the shipped version." The sync_skills() message should suggest hermes skills reset --restore instead of plain reset.
Option B (behavioral fix): Have reset without --restore record the current bundled hash in the manifest, so the next sync sees user_hash != origin_hash and processes it through the existing user-modified path (which at least warns predictably) rather than falling through to the new-skill-conflict path each time. This makes the behavior consistent and understandable.
Option C (full fix): Combine A + B, and add a --preserve-mine / --accept-upstream flag on reset to let the user choose explicitly.
Related Issues
Operating System
macOS 15.x
Hermes Version
Latest main
Bug Description
When a user modifies a bundled skill (e.g. edits the SKILL.md in
~/.hermes/skills/),sync_skills()detects the hash mismatch and correctly marks it as user-modified, skipping future updates. However,hermes skills reset <name>(without--restore) claims to resolve this but actually does nothing for truly modified skills.Two misleading messages are involved:
reset_bundled_skill()returns: "Future hermes update runs will re-baseline against your current copy and accept upstream changes." — This is false when the skill was actually modified. The nextsync_skills()call hits thenot_in_manifest + dest.exists() + dir_hash(dest) != bundled_hashbranch, which skips the skill without writing a manifest entry. The skill remains permanently skipped for future updates.sync_skills()prints: "yours was kept. Runhermes skills reset {name}to replace it with the bundled version." — Butresetwithout--restoredoes not replace it. The user gets a circular suggestion: runreset→ it says "yours was kept, run reset."Steps to Reproduce
foois synced to~/.hermes/skills/foo/.~/.hermes/skills/foo/SKILL.mdso the user hash diverges from the origin hash.~ foo (user-modified, skipping)is printed.hermes skills reset foo(no--restore). Output says "Cleared manifest entry for 'foo'. Future hermes update runs will re-baseline..."~ foo (user-modified, skipping)again. Nothing changed.Expected Behavior
hermes skills reset <name>without--restoreshould either:--restoreto revert to the shipped version."Actual Behavior
The command returns a success message that is demonstrably false for user-modified skills. The skill remains permanently stuck in "user-modified" limbo, and the command output implies a resolution that never happens.
Affected Component
Skills (skill loading, skill hub, skill guard)
Root Cause Analysis
Two locations in
tools/skills_sync.py:1. Lines 409-411 —
reset_bundled_skill()non---restorepath:This is incorrect when the skill was modified. Clearing the manifest causes
sync_skills()to treat it as a new skill conflict (lines 204-226), which skips the skill without writing a manifest entry when_dir_hash(dest) != bundled_hash. The next sync repeats the same loop.2. Lines 222-225 —
sync_skills()new-skill-conflict path:Suggesting
resetis circular—resetwithout--restorecannot replace anything for modified skills. The correct suggestion should bereset --restore.Proposed Fix
Option A (honest messaging, minimal fix): Fix the two messages to be accurate about what happens. The
reset_bundled_skill()message should say something like: "Cleared manifest entry for '{name}'. If you modified this skill, your local copy is preserved and will not receive upstream updates. Use--restoreto revert to the shipped version." Thesync_skills()message should suggesthermes skills reset --restoreinstead of plainreset.Option B (behavioral fix): Have
resetwithout--restorerecord the current bundled hash in the manifest, so the next sync seesuser_hash != origin_hashand processes it through the existing user-modified path (which at least warns predictably) rather than falling through to the new-skill-conflict path each time. This makes the behavior consistent and understandable.Option C (full fix): Combine A + B, and add a
--preserve-mine/--accept-upstreamflag onresetto let the user choose explicitly.Related Issues
Operating System
macOS 15.x
Hermes Version
Latest main