hermes - 💡(How to fix) Fix get_hermes_dir backward-compat resolver silently shadows new-path data with empty old-path dirs

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…

hermes_constants.get_hermes_dir(new_subpath, old_name) returns the old path as soon as it exists on disk, even when empty. When something — agent upgrade, profile init, manual mkdir — creates an empty directory at the old flat path on a profile that already has populated data at the new consolidated path, the resolver silently flips to the empty old dir and the data becomes invisible. No log entry, no error, no warning. The feature behaves as if state was wiped.

Reproduced on a profile that lost a Discord pairing approval invisibly for ~35 hours before the user noticed.

Error Message

hermes_constants.get_hermes_dir(new_subpath, old_name) returns the old path as soon as it exists on disk, even when empty. When something — agent upgrade, profile init, manual mkdir — creates an empty directory at the old flat path on a profile that already has populated data at the new consolidated path, the resolver silently flips to the empty old dir and the data becomes invisible. No log entry, no error, no warning. The feature behaves as if state was wiped.

Root Cause

hermes_constants.get_hermes_dir(new_subpath, old_name) returns the old path as soon as it exists on disk, even when empty. When something — agent upgrade, profile init, manual mkdir — creates an empty directory at the old flat path on a profile that already has populated data at the new consolidated path, the resolver silently flips to the empty old dir and the data becomes invisible. No log entry, no error, no warning. The feature behaves as if state was wiped.

Reproduced on a profile that lost a Discord pairing approval invisibly for ~35 hours before the user noticed.

Code Example

~/.hermes/profiles/hermes-cs/
  platforms/pairing/discord-approved.json   # populated, Birth 2026-05-11 14:22
  # no `pairing/` dir exists

---

pairing/        <- created empty
audio_cache/    <- created empty
hooks/          <- created empty
image_cache/    <- created empty

---

PAIRING_DIR = get_hermes_dir("platforms/pairing", "pairing")

---

def get_hermes_dir(new_subpath: str, old_name: str) -> Path:
    home = get_hermes_home()
    old_path = home / old_name
    if old_path.exists():           # True for empty dirs
        return old_path
    return home / new_subpath

---

if old_path.exists() and any(old_path.iterdir()):
       return old_path
RAW_BUFFERClick to expand / collapse

Summary

hermes_constants.get_hermes_dir(new_subpath, old_name) returns the old path as soon as it exists on disk, even when empty. When something — agent upgrade, profile init, manual mkdir — creates an empty directory at the old flat path on a profile that already has populated data at the new consolidated path, the resolver silently flips to the empty old dir and the data becomes invisible. No log entry, no error, no warning. The feature behaves as if state was wiped.

Reproduced on a profile that lost a Discord pairing approval invisibly for ~35 hours before the user noticed.

Repro timeline

Profile state at 2026-05-11 (post-install):

~/.hermes/profiles/hermes-cs/
  platforms/pairing/discord-approved.json   # populated, Birth 2026-05-11 14:22
  # no `pairing/` dir exists

get_hermes_dir("platforms/pairing", "pairing") correctly returns ~/.hermes/profiles/hermes-cs/platforms/pairing/. Bot recognizes approved Discord user.

Then on 2026-05-16 10:43:24, four directories were created simultaneously (same nanosecond — clearly a batch operation, presumably profile init / agent upgrade hook):

pairing/        <- created empty
audio_cache/    <- created empty
hooks/          <- created empty
image_cache/    <- created empty

After that point, get_hermes_dir("platforms/pairing", "pairing") returns ~/.hermes/profiles/hermes-cs/pairing/ (empty) instead of ~/.hermes/profiles/hermes-cs/platforms/pairing/ (populated). Bot no longer recognizes the previously-approved Discord user. Next DM (~35 hours later) triggers fresh pairing flow as if the user was never approved.

I haven't traced what code creates the four empty dirs — would be useful context.

Code paths

gateway/pairing.py:47

PAIRING_DIR = get_hermes_dir("platforms/pairing", "pairing")

hermes_constants.py:124

def get_hermes_dir(new_subpath: str, old_name: str) -> Path:
    home = get_hermes_home()
    old_path = home / old_name
    if old_path.exists():           # True for empty dirs
        return old_path
    return home / new_subpath

Class bug — same resolver used by 11+ call sites

Each is exposed to the same silent shadowing:

  • gateway/pairing.py:47pairing / platforms/pairing
  • gateway/platforms/base.py:546image_cache / cache/images
  • gateway/platforms/base.py:687audio_cache / cache/audio
  • gateway/platforms/base.py:780video_cache / cache/videos
  • gateway/platforms/base.py:813document_cache / cache/documents
  • gateway/platforms/matrix.py:130matrix/store / platforms/matrix/store
  • gateway/platforms/whatsapp.py:260whatsapp/session / platforms/whatsapp/session
  • tools/vision_tools.py:574,716,1214 — vision/video temp dirs
  • tools/credential_files.py:366,417 — credential storage paths
  • tools/tts_tool.py:181,1420 — audio caches
  • tools/browser_tool.py:3066,3094 — browser screenshots

Any of these can drop user-visible state the same way if an empty dir gets created at the legacy flat path.

Proposed fixes (any one closes the hole)

  1. Check non-empty in the resolver — treat empty old dir as not-present:
    if old_path.exists() and any(old_path.iterdir()):
        return old_path
  2. Migrate on detection — if both paths exist and only one has data, copy data to the preferred path and emit a warning. Removes the ambiguity instead of papering over it.
  3. Stop pre-creating empty old dirs — find whatever runs at startup and creates pairing/, audio_cache/, image_cache/, hooks/ unconditionally; skip when the profile already uses the new consolidated layout.

Environment

  • hermes-agent commit bc7c608d5 (HEAD at time of repro)
  • Linux, multi-profile install (default + merlin + hermes-cs + hermes-wb + doc)
  • Profiles affected by the shadow: hermes-cs (pairing lost — user-visible), hermes-wb (latent — empty-dir shadow present but no original data at the now-shadowed path)

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 - 💡(How to fix) Fix get_hermes_dir backward-compat resolver silently shadows new-path data with empty old-path dirs