Skip to content

fix(skills_guard): sort paths as strings to match bundle_content_hash ordering - #64659

Closed
waiwaic wants to merge 1 commit into
NousResearch:mainfrom
waiwaic:fix/content-digest-path-sort
Closed

fix(skills_guard): sort paths as strings to match bundle_content_hash ordering#64659
waiwaic wants to merge 1 commit into
NousResearch:mainfrom
waiwaic:fix/content-digest-path-sort

Conversation

@waiwaic

@waiwaic waiwaic commented Jul 14, 2026

Copy link
Copy Markdown

Problem

_content_digest() in tools/skills_guard.py sorts Path objects via sorted(skill_path.rglob("*")), while bundle_content_hash() in tools/skills_hub.py sorts string keys via sorted(bundle.files). When a skill directory contains a file and a subdirectory sharing a name prefix (e.g. styles.md and styles/), these two sort orders produce different sequences, which causes the two functions to compute different digests for identical skill content.

Root cause

Path.__lt__ compares paths component-wise (by POSIX path components), whereas string comparison is character-by-character. Given a file styles.md and a subdirectory styles/x.md:

  • Path sorting[styles/x.md, styles.md]
    (component ("styles", "x.md") vs ("styles.md",)"styles" is a prefix of "styles.md", so the shorter tuple first element makes styles/x.md sort first)
  • String sorting["styles.md", "styles/x.md"]
    ("." (0x2E) < "/" (0x2F), so styles.md sorts first)

Impact

  • Skills with files that have both a standalone file and a subdirectory sharing the same prefix will trigger false-positive "update available" or false cache invalidation in check_for_skill_updates().
  • The existing test test_bundle_content_hash_matches_installed_content_hash did not catch this because it uses references/ (no references.md at top level).

Fix

Add key=lambda p: p.relative_to(skill_path).as_posix() to the sorted() call in _content_digest(), so both hash functions sort by relative path strings using the same comparison.

Verification

All three scenarios pass after the fix:

Scenario Disk hash Bundle hash Match
styles.md + styles/x.md (bug case) sha256:1f2c9d8... sha256:1f2c9d8...
SKILL.md + references/checklist.md (existing test) sha256:d14a9ea... sha256:d14a9ea...
a.py + a.cpp + a/b.py (edge case) sha256:1bc0cef... sha256:1bc0cef...

… ordering

_content_digest() sorted Path objects via sorted(skill_path.rglob('*')),
which uses POSIX component-wise comparison.  bundle_content_hash() sorts
string keys (sorted(bundle.files)).  When a skill directory contains a file
and a subdirectory sharing a name prefix (e.g. styles.md and styles/x.md),
the two sort orders differ, causing the same skill content to produce
different digests on each side.

Fix by adding a key=lambda that converts each Path to its relative path
string before sorting, so _content_digest stays symmetric with
bundle_content_hash.  Tag the docstring with a comment explaining why.

Fixes: hash mismatch between content_hash() and bundle_content_hash()
for skills with files whose names are prefixes of subdirectory names.
@waiwaic

waiwaic commented Jul 14, 2026

Copy link
Copy Markdown
Author

Closing: this was submitted in error / no longer needed.

@waiwaic waiwaic closed this Jul 14, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Duplicate of #53448 — both PRs fix the same content_hash/_content_digest() symmetry bug in tools/skills_guard.py with the identical mechanism (sort by the relative-posix STRING via a key= lambda so the on-disk digest matches bundle_content_hash()'s string-key sort in tools/skills_hub.py). #53448 (open, triaged 2026-06-27, also Fixes #53404) is the earlier canonical entrant. Related: #53404 (the underlying issue), #59354 (the mutually-exclusive in-memory-side fix, now closed), #53877 (byte-equivalent closed sibling).

@alt-glitch alt-glitch added type/bug Something isn't working tool/skills Skills system (list, view, manage) P3 Low — cosmetic, nice to have duplicate This issue or pull request already exists labels Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

duplicate This issue or pull request already exists P3 Low — cosmetic, nice to have tool/skills Skills system (list, view, manage) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants