hermes - ✅(Solved) Fix Bug: Skill loader discovers .bak-* backup directories before live skills [2 pull requests, 1 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#25113Fetched 2026-05-14 03:48:50
View on GitHub
Comments
0
Participants
1
Timeline
5
Reactions
0
Participants
Timeline (top)
labeled ×3cross-referenced ×2

When a .bak-* directory exists alongside a live skill directory with the same base name (e.g., hermes-profile-convergence.bak-20260510_234206 and hermes-profile-convergence), the Hermes skill loader may discover the backup directory instead of the live skill. This causes stale skill content to be loaded without warning.

This affected actual usage: the hermes-profile-convergence skill loaded its v2.0.0 backup instead of the live v3.1.0, causing the agent to follow outdated instructions (3-profile scope, old Decision table instead of the new Decision Engine).

Root Cause

The skill loader appears to use glob/discovery that does not exclude .bak-* directories. Since hermes-profile-convergence.bak-* starts with the same prefix as the live hermes-profile-convergence skill, it can match first in search order.

Fix Action

Fix / Workaround

Workaround (applied locally)

Moved all .bak-* directories from the skills/ tree into skills/__backups__/<category>/ — the current glob pattern (skills/*/*/) does not reach into __backups__/.

PR fix notes

PR #25140: fix(skills): exclude .bak-/.backup- directories from skill discovery (#25113)

Description (problem / solution / changelog)

Summary

hermes profile and hermes update leave .bak-<timestamp> (or <skill>.bak-<suffix>) snapshots alongside live skills. The skill loader walks the tree and yields them in alphabetic order, so a backup SKILL.md can shadow the live one — reporter saw hermes-profile-convergence load its v2.0.0 backup instead of the live v3.1.0.

The bug

agent/skill_utils.py:iter_skill_index_files excludes .git, .github, .hub, .archive but not backup directories. The os.walk descends into .bak-20260510_233500, finds its SKILL.md, and yields it before the live skill's. Discovery is centralised here, so every caller (skills_tool._find_all_skills, prompt_builder, the skill_commands platform scan) inherits the bug.

The fix

Add _is_backup_dir(name) — a narrow substring check on .bak- and .backup- (with the trailing dash, so notebook / lookback-tool / bak-without-dot are NOT collateral) — and apply it alongside EXCLUDED_SKILL_DIRS in the dir-pruning step. Two-line change at the walk site; the helper sits next to EXCLUDED_SKILL_DIRS with a comment explaining the upgrade-path origin.

Test plan

  • Focused: tests/agent/test_skill_utils.py — three concrete patterns from the issue (.bak-<ts> sibling, <skill>.bak-<suffix> peer, .backup-<ts>) all confirm the live skill is the only one yielded.
  • Non-regression: bak-without-dot, lookback-tool, notebook-skill are all still picked up.
  • Adjacent: tests/agent/test_skill_utils.py tests/agent/test_external_skills.py tests/agent/test_external_skills_dirs_cache.py tests/test_plugin_skills.py — 53 passed locally.
  • Regression guard: with the fix reverted, the three new tests fail with result == ['stale', 'live'] (backup shadows live). With the fix, all 8 pass.

Related / Positioning

  • Fixes #25113.

  • Competing PR: #25143 (zccyman). Same root cause, different approach. Honest comparison for the maintainer:

    aspectthis PR (#25140)#25143
    scopeagent/skill_utils.py onlyagent/skill_utils.py + tools/skills_tool.py (helper duplicated, kept in sync by comment)
    match logic.bak- / .backup- substring (trailing dash, conservative).bak / .backup- / backup- case-insensitive substring (broader; foo.bakery would be excluded)
    also excludes(none added)__pycache__
    diff size+47 / -2+164 / -4
  • Why this PR is correct without touching tools/skills_tool.py: that file already calls iter_skill_index_files for its scan, so fixing the walker covers it. The local _EXCLUDED_SKILL_DIRS check in tools/skills_tool.py:_find_all_skills is a redundant secondary filter on already-yielded paths — backup dirs are pruned before they reach it.

Sibling code paths that may need the same fix if the maintainer prefers defense-in-depth: the duplicated _EXCLUDED_SKILL_DIRS frozenset in tools/skills_tool.py (and __pycache__ could be added to EXCLUDED_SKILL_DIRS in agent/skill_utils.py as a separate small change). Intentionally left out of this PR's scope to keep the diff small — happy to widen if preferred.

Changed files

  • agent/skill_utils.py (modified, +15/-2)
  • tests/agent/test_skill_utils.py (modified, +64/-1)

PR #25143: fix(skills): exclude .bak-* and backup directories from skill discovery

Description (problem / solution / changelog)

Summary

The skill loader's EXCLUDED_SKILL_DIRS only excluded .git, .github, .hub, and .archive. Backup directories matching patterns like my-skill.bak-20260510 were not excluded, causing the stale backup version to be discovered and loaded instead of (or alongside) the live skill.

Before

EXCLUDED_SKILL_DIRS = frozenset((".git", ".github", ".hub", ".archive"))

A directory like hermes-profile-convergence.bak-20260510_234206 would pass the filter and its SKILL.md would be discovered as a valid skill.

After

Two-layer exclusion:

  1. Static set — added __pycache__ to the existing frozenset
  2. Pattern matching — new _is_excluded_skill_dir() checks for .bak, .backup-, and backup- anywhere in the directory name (case-insensitive)
EXCLUDED_SKILL_DIRS = frozenset((".git", ".github", ".hub", ".archive", "__pycache__"))
_EXCLUDED_DIR_PREFIXES = (".bak", ".backup-", "backup-")

Applied to both discovery paths:

  • agent/skill_utils.pyiter_skill_index_files()
  • tools/skills_tool.py — inline scan in skill listing

Impact

ScenarioBeforeAfter
my-skill.bak-20260510/SKILL.mdDiscovered and loadedSkipped
my-skill.backup-v2/SKILL.mdDiscovered and loadedSkipped
__pycache__/SKILL.md (unlikely but defensive)DiscoveredSkipped
my-skill/SKILL.md (live)DiscoveredDiscovered ✓

Testing

  • 8 new tests:
    • 5 unit tests for _is_excluded_skill_dir() (exact, normal, .bak, .backup, case-insensitive)
    • 3 integration tests for iter_skill_index_files() (bak dirs, pycache, nested)
  • 2827 existing agent tests (7 pre-existing env failures unrelated to this change)
  • 0 regressions

Files changed

FileChange
agent/skill_utils.pyAdd pattern exclusion + update filter
tools/skills_tool.pySync exclusion logic + update filter
tests/agent/test_skill_discovery_excludes.pyNew: 8 tests

Closes #25113

Changed files

  • agent/skill_utils.py (modified, +25/-2)
  • tests/agent/test_skill_discovery_excludes.py (added, +123/-0)
  • tools/skills_tool.py (modified, +16/-2)
RAW_BUFFERClick to expand / collapse

Bug Report: Skill loader discovers .bak-* backup directories and may load stale skill version

Description

When a .bak-* directory exists alongside a live skill directory with the same base name (e.g., hermes-profile-convergence.bak-20260510_234206 and hermes-profile-convergence), the Hermes skill loader may discover the backup directory instead of the live skill. This causes stale skill content to be loaded without warning.

This affected actual usage: the hermes-profile-convergence skill loaded its v2.0.0 backup instead of the live v3.1.0, causing the agent to follow outdated instructions (3-profile scope, old Decision table instead of the new Decision Engine).

Root Cause

The skill loader appears to use glob/discovery that does not exclude .bak-* directories. Since hermes-profile-convergence.bak-* starts with the same prefix as the live hermes-profile-convergence skill, it can match first in search order.

Affected Skills (verified across profile skill trees)

  • hermes-agent.bak-20260510_233500
  • hermes-profile-convergence.bak-skill-refinement-20260510_234206
  • end-session-mempalace-checkpoint.bak-20260510_233500
  • live-site-health-check.bak-20260510_233500
  • resume-project-audit.bak-20260510_233500

Impact

  • Stale/outdated skill content may be loaded
  • Agent follows wrong instructions
  • User may not detect the mismatch until behavior diverges from expectations
  • Backup directories proliferate across profile skill trees (~5 per profile × 4 profiles + canonical = ~23 copies)

Suggested Fix

The skill loader should exclude directories matching *.bak-* or *.backup-* from discovery, similar to how the convergence skill already excludes them from comparison. Alternatively, add a discovery exclusion list for common backup patterns like .bak-*, .backup-*, __pycache__, etc.

Workaround (applied locally)

Moved all .bak-* directories from the skills/ tree into skills/__backups__/<category>/ — the current glob pattern (skills/*/*/) does not reach into __backups__/.

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

hermes - ✅(Solved) Fix Bug: Skill loader discovers .bak-* backup directories before live skills [2 pull requests, 1 participants]