Skip to content

Bug: hermes skills check reports permanent false-positive update_available on Windows (path-separator hash divergence) #62310

Description

@diegokolling

Bug: hermes skills check reports permanent false-positive update_available on Windows

Summary

After a successful hermes skills update (e.g. "Updated 32 skill(s)"), hermes skills check continues listing ALL those skills as update_available — forever. The hashes never match on Windows.

Root cause

Two content-hash functions — documented in-code as "MUST stay symmetric" — diverge on Windows due to path-separator and sort-order differences:

tools/skills_hub.py::bundle_content_hash (in-memory bundle, L3573):

  • Iterates sorted(bundle.files) — keys retain backslash separators on Windows (e.g. references\methods\x.md)
  • sorted() on strings is case-sensitive (SKILL.md sorts before references/)

tools/skills_guard.py::content_hash (on-disk, L766):

  • Uses f.relative_to(skill_path).as_posix() — keys always have forward slash (e.g. references/methods/x.md)
  • sorted(skill_path.rglob("*")) sorts Path objects, which is case-insensitive on Windows (references/ sorts before SKILL.md)

The relative path is mixed into the SHA-256, so for any skill with files in subdirectories, the two digests can never match on Windows. On POSIX both functions agree, so the bug is invisible on Linux/macOS.

Proof

Recomputed the correct symmetric hash for all 32 flagged skills → 32/32 byte-identical to the registry, 0 genuinely outdated. After applying the fix and re-recording lock hashes: hermes skills check reports "0 update(s) available across 55 skills". Tamper test confirms genuine content changes are still detected.

Fix

In BOTH bundle_content_hash and content_hash, normalize the rel-path key to / and sort by that normalized string key:

# bundle_content_hash (skills_hub.py):
for rel_path in sorted(bundle.files, key=lambda p: p.replace("\\", "/")):
    normalized = rel_path.replace("\\", "/")
    h.update(normalized.encode("utf-8"))
    h.update(b"\x00")
    ...

# content_hash (skills_guard.py):
for f in sorted(skill_path.rglob("*"), key=lambda p: p.relative_to(skill_path).as_posix().replace("\\", "/")):
    if f.is_file():
        rel = f.relative_to(skill_path).as_posix()
        h.update(rel.encode("utf-8"))
        ...

Add a sys.platform-guarded test with a mixed-separator + mixed-case fixture asserting bundle_content_hash(bundle) == content_hash(disk).

Environment

  • Windows 10/11 (any install with hub skills containing subdirectory files)
  • Verified against current main

Attribution

Diego Kolling + Hermes Agent (Nous subscription)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Medium — degraded but workaround existsplatform/windowsNative Windows-specific behavior or breakagesweeper:risk-platform-windowsSweeper risk: may break or behave differently on native Windowstool/skillsSkills system (list, view, manage)type/bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions