hermes - ✅(Solved) Fix AttributeError in bundle_content_hash when checking skill updates [3 pull requests, 2 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#19073Fetched 2026-05-04 05:18:17
View on GitHub
Comments
2
Participants
2
Timeline
14
Reactions
0
Author
Timeline (top)
cross-referenced ×4labeled ×4commented ×2mentioned ×1

Error Message

% hermes skills check Traceback (most recent call last): File "/Users/magnus/.local/bin/hermes", line 10, in <module> sys.exit(main()) File ".../hermes_cli/main.py", line 10422, in main args.func(args) File ".../hermes_cli/main.py", line 9241, in cmd_skills skills_command(args) File ".../hermes_cli/skills_hub.py", line 1336, in skills_command do_check(name=getattr(args, "name", None)) File ".../hermes_cli/skills_hub.py", line 865, in do_check results = check_for_skill_updates(name=name) File ".../tools/skills_hub.py", line 2857, in check_for_skill_updates latest_hash = bundle_content_hash(bundle) File ".../tools/skills_hub.py", line 2804, in bundle_content_hash h.update(bundle.files[rel_path].encode("utf-8")) AttributeError: 'bytes' object has no attribute 'encode'. Did you mean: 'decode'?

Root Cause

SkillBundle.files is typed as Dict[str, Union[str, bytes]] (line 81 of tools/skills_hub.py). The dict values can be either str or bytes depending on how the bundle was loaded. However, bundle_content_hash() (lines 2800-2805) unconditionally calls .encode("utf-8") on every value, which blows up when the value is already bytes.

Fix Action

Fix

Check the type before encoding:

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):
        content = bundle.files[rel_path]
        if isinstance(content, bytes):
            h.update(content)
        else:
            h.update(content.encode("utf-8"))
    return f"sha256:{h.hexdigest()[:16]}"

PR fix notes

PR #19079: fix: handle bytes values in bundle_content_hash()

Description (problem / solution / changelog)

Summary

bundle_content_hash() crashes with AttributeError when SkillBundle.files contains bytes values (e.g. binary assets like images).

Fixes #13408 Fixes #19073

Root Cause

bundle_content_hash() (line 2804) unconditionally calls .encode("utf-8") on every file content, but SkillBundle.files is typed as Dict[str, Union[str, bytes]]. Any bundle with raw bytes values crashes.

Fix

Check isinstance(content, bytes) before encoding — if already bytes, hash directly.

Test

Added test_bundle_content_hash_handles_bytes_values covering mixed str + bytes bundle files.

Validation

  • scripts/run_tests.sh tests/tools/test_skills_hub.py — 104 passed
  • py_compile both files — OK
  • Note: PR #13531 addresses the same bug but is still in DRAFT since April.

Changed files

  • tests/tools/test_skills_hub.py (modified, +16/-0)
  • tools/skills_hub.py (modified, +5/-1)

PR #19081: fix(skills): handle bytes values in bundle_content_hash

Description (problem / solution / changelog)

Fix: Handle bytes values in bundle_content_hash

SkillBundle.files is typed as Dict[str, Union[str, bytes]] — dict values can be either str or bytes depending on how the bundle was loaded. However, bundle_content_hash() unconditionally called .encode("utf-8") on every value, causing AttributeError: 'bytes' object has no attribute 'encode' when the value was already bytes.

Before:

h.update(bundle.files[rel_path].encode("utf-8"))

After:

content = bundle.files[rel_path]
if isinstance(content, bytes):
    h.update(content)
else:
    h.update(content.encode("utf-8"))

Fixes #19073

Changed files

  • tools/skills_hub.py (modified, +5/-1)

PR #19145: fix(skills): handle bytes in bundle update hash

Description (problem / solution / changelog)

Summary

  • handle SkillBundle.files entries that are already bytes in bundle_content_hash()
  • add regression coverage for byte-backed bundle files so skill update checks stop crashing

Verification

  • scripts/run_tests.sh tests/tools/test_skills_hub.py -k 'bundle_content_hash or check_for_skill_updates'
  • scripts/run_tests.sh tests/tools/test_skills_hub.py
  • independent delegated code review: passed

Closes #19090 Related to #19073

Changed files

  • tests/tools/test_skills_hub.py (modified, +21/-0)
  • tools/skills_hub.py (modified, +5/-1)

Code Example

% hermes skills check
Traceback (most recent call last):
  File "/Users/magnus/.local/bin/hermes", line 10, in <module>
    sys.exit(main())
  File ".../hermes_cli/main.py", line 10422, in main
    args.func(args)
  File ".../hermes_cli/main.py", line 9241, in cmd_skills
    skills_command(args)
  File ".../hermes_cli/skills_hub.py", line 1336, in skills_command
    do_check(name=getattr(args, "name", None))
  File ".../hermes_cli/skills_hub.py", line 865, in do_check
    results = check_for_skill_updates(name=name)
  File ".../tools/skills_hub.py", line 2857, in check_for_skill_updates
    latest_hash = bundle_content_hash(bundle)
  File ".../tools/skills_hub.py", line 2804, in bundle_content_hash
    h.update(bundle.files[rel_path].encode("utf-8"))
AttributeError: 'bytes' object has no attribute 'encode'. Did you mean: 'decode'?

---

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):
        content = bundle.files[rel_path]
        if isinstance(content, bytes):
            h.update(content)
        else:
            h.update(content.encode("utf-8"))
    return f"sha256:{h.hexdigest()[:16]}"
RAW_BUFFERClick to expand / collapse

Bug Description

Running hermes skills check crashes with an AttributeError in tools/skills_hub.py.

Steps to Reproduce

  1. Have one or more skills installed from the hub
  2. Run hermes skills check
  3. Observe crash
% hermes skills check
Traceback (most recent call last):
  File "/Users/magnus/.local/bin/hermes", line 10, in <module>
    sys.exit(main())
  File ".../hermes_cli/main.py", line 10422, in main
    args.func(args)
  File ".../hermes_cli/main.py", line 9241, in cmd_skills
    skills_command(args)
  File ".../hermes_cli/skills_hub.py", line 1336, in skills_command
    do_check(name=getattr(args, "name", None))
  File ".../hermes_cli/skills_hub.py", line 865, in do_check
    results = check_for_skill_updates(name=name)
  File ".../tools/skills_hub.py", line 2857, in check_for_skill_updates
    latest_hash = bundle_content_hash(bundle)
  File ".../tools/skills_hub.py", line 2804, in bundle_content_hash
    h.update(bundle.files[rel_path].encode("utf-8"))
AttributeError: 'bytes' object has no attribute 'encode'. Did you mean: 'decode'?

Root Cause

SkillBundle.files is typed as Dict[str, Union[str, bytes]] (line 81 of tools/skills_hub.py). The dict values can be either str or bytes depending on how the bundle was loaded. However, bundle_content_hash() (lines 2800-2805) unconditionally calls .encode("utf-8") on every value, which blows up when the value is already bytes.

Fix

Check the type before encoding:

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):
        content = bundle.files[rel_path]
        if isinstance(content, bytes):
            h.update(content)
        else:
            h.update(content.encode("utf-8"))
    return f"sha256:{h.hexdigest()[:16]}"

Environment

  • OS: macOS (Darwin 24.6.0)
  • Hermes Agent: installed from source in ~/.hermes/hermes-agent/
  • Skills installed: docker-management, fastmcp, mcporter, obsidian-cli, obsidian-markdown, obsidian-bases

This issue was filed by Hermes Agent (Aldous) on behalf of Magnus Hedemark (@magnus919) — they found this while trying to check for skill updates.

extent analysis

TL;DR

Update the bundle_content_hash function to conditionally encode the content based on its type.

Guidance

  • Verify the issue by checking the type of bundle.files[rel_path] before calling encode("utf-8") on it.
  • Update the bundle_content_hash function as shown in the provided fix to handle both str and bytes types.
  • Test the updated function with different types of bundle files to ensure it works correctly.
  • Consider adding a check to handle any potential encoding errors that may occur when encoding string content.

Example

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):
        content = bundle.files[rel_path]
        if isinstance(content, bytes):
            h.update(content)
        else:
            h.update(content.encode("utf-8"))
    return f"sha256:{h.hexdigest()[:16]}"

Notes

The provided fix assumes that the bundle_content_hash function is the only place where this issue occurs. However, it's possible that similar issues may exist in other parts of the codebase where bundle.files values are being encoded.

Recommendation

Apply the provided workaround by updating the bundle_content_hash function to conditionally encode the content based on its type, as this directly addresses the identified root cause of the issue.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING