From cb0e225e7864108350786737a8c703b8a2cc0425 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Tue, 21 Apr 2026 08:43:51 +0000 Subject: [PATCH] fix(skills_hub): handle bytes content in bundle_content_hash What broke: Running `hermes skills update` crashed with AttributeError: 'bytes' object has no attribute 'encode' when skill bundles contained binary files (e.g. audio assets from official skills). Root cause: SkillBundle.files is defined as Dict[str, Union[str, bytes]], but bundle_content_hash() assumed all file content was str and called .encode("utf-8") on every value. When the value was already bytes, this caused a crash. Why this fix is minimal: Single function fix - detect type and route bytes directly to hash.update(), encode strings as before. No broader changes to SkillBundle, file handling, or any other hash computation paths. What I tested: Added regression test verifying bytes content is handled correctly and produces deterministic hashes. What I intentionally did not change: - No changes to SkillBundle class or file loading logic - No changes to other hash computation functions - No changes to quarantine/installation logic --- tests/tools/test_skills_hub.py | 29 +++++++++++++++++++++++++++++ tools/skills_hub.py | 7 ++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/tests/tools/test_skills_hub.py b/tests/tools/test_skills_hub.py index 24d1e87affcc8..32115b9a254c7 100644 --- a/tests/tools/test_skills_hub.py +++ b/tests/tools/test_skills_hub.py @@ -674,6 +674,35 @@ def fake_get(url, *args, **kwargs): class TestCheckForSkillUpdates: + def test_bundle_content_hash_handles_bytes_content(self): + """Regression test for #2739: bundle_content_hash must handle bytes files.""" + # SkillBundle.files can contain Union[str, bytes] + bundle = SkillBundle( + name="binary-skill", + files={ + "SKILL.md": "---\nname: test\n---\n", + "assets/audio.wav": b"RIFF\x00\x01fakewav", # bytes content + }, + source="github", + identifier="owner/repo/binary-skill", + trust_level="community", + ) + # Should not crash with AttributeError: 'bytes' object has no attribute 'encode' + hash_result = bundle_content_hash(bundle) + assert hash_result.startswith("sha256:") + # Deterministic: same content = same hash + bundle2 = SkillBundle( + name="binary-skill", + files={ + "SKILL.md": "---\nname: test\n---\n", + "assets/audio.wav": b"RIFF\x00\x01fakewav", + }, + source="github", + identifier="owner/repo/binary-skill", + trust_level="community", + ) + assert bundle_content_hash(bundle2) == hash_result + def test_bundle_content_hash_matches_installed_content_hash(self, tmp_path): from tools.skills_guard import content_hash diff --git a/tools/skills_hub.py b/tools/skills_hub.py index 47aef8075b7c1..8724ff7aeadc2 100644 --- a/tools/skills_hub.py +++ b/tools/skills_hub.py @@ -2630,7 +2630,12 @@ def bundle_content_hash(bundle: SkillBundle) -> str: """Compute a deterministic hash for an in-memory skill bundle.""" h = hashlib.sha256() for rel_path in sorted(bundle.files): - h.update(bundle.files[rel_path].encode("utf-8")) + content = bundle.files[rel_path] + # files can contain str or bytes; handle both + if isinstance(content, bytes): + h.update(content) + else: + h.update(content.encode("utf-8")) return f"sha256:{h.hexdigest()[:16]}"