hermes - ✅(Solved) Fix [Bug]: Path.home() / ".hermes" bypasses get_hermes_home() in 23 production files [1 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#18060Fetched 2026-05-01 05:54:08
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
1
Participants
Timeline (top)
labeled ×5cross-referenced ×1

Error Message

Additional Logs / Traceback (optional)

Root Cause

Root Cause Analysis (optional)

Fix Action

Fixed

PR fix notes

PR #18068: fix(paths): replace Path.home()/.hermes with get_hermes_home() across…

Description (problem / solution / changelog)

What does this PR do?

Fixes 23 production callsites that hardcoded Path.home() / ".hermes" instead of calling the canonical get_hermes_home() from hermes_constants, silently ignoring the HERMES_HOME environment variable.

get_hermes_home() is already used correctly 239 times across the codebase — these 23 were the outliers. The fix is purely mechanical: no logic changes, no behavior change for default single-user installs where HERMES_HOME is unset.

What was broken:

  • Docker deployments — sessions, state DB, and channel directory were always written to ~/.hermes even when HERMES_HOME=/data (or any custom path) was set
  • Profile isolation (hermes -p <profile>) — the profile-specific HERMES_HOME was bypassed at these callsites, causing profile data to bleed into the default home
  • CI test isolationtests/conftest.py sets HERMES_HOME to a per-test tmpdir; these callsites ignored it and read/wrote the real ~/.hermes, contaminating production state during test runs

Three callsites that use Path.home() / ".hermes" intentionally are left unchanged and annotated with explanatory comments:

  • hermes_cli/auth.py:735 — pytest seat belt that compares against the real home to refuse touching production auth during test runs
  • hermes_cli/gateway.py:1512 — system service install remapping that must compare against the literal default, not the current HERMES_HOME
  • plugins/memory/honcho/client.py:76 — falls back to the default profile's config so profile-isolated instances inherit host blocks from setup/clone

Related Issue

Fixes #18060

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • hermes_cli/env_loader.py — add from hermes_constants import get_hermes_home; replace hardcoded path in load_hermes_dotenv() with Path(hermes_home) if hermes_home else get_hermes_home()
  • hermes_cli/main.py — use already-imported get_hermes_home() in the Node.js bootstrap helper (line 980)
  • hermes_cli/slack_cli.py — fix except Exception fallback to use or pattern; add missing import os
  • hermes_cli/gateway.py — annotate intentional literal-default comparison with comment
  • hermes_cli/auth.py — annotate intentional real-home safety guard with comment
  • mcp_serve.py — fix three except ImportError fallbacks (lines 68, 105, 349) to use os.environ.get("HERMES_HOME") or str(...) pattern
  • tools/mcp_oauth.py — fix except ImportError fallback (line 104)
  • plugins/hermes-achievements/dashboard/plugin_api.py — add get_hermes_home() with ImportError shim; fix state_path(), snapshot_path(), checkpoint_path() (lines 138, 142, 146)
  • plugins/memory/honcho/client.py — annotate intentional default-profile fallback with comment
  • scripts/discord-voice-doctor.py — add get_hermes_home() with ImportError shim; replace module-level HERMES_HOME assignment
  • scripts/profile-tui.py — add get_hermes_home() with ImportError shim; fix DEFAULT_LOG (line 40) and DEFAULT_STATE_DB (line 41)
  • skills/red-teaming/godmode/scripts/auto_jailbreak.py — fix two occurrences (lines 38 and 60)
  • skills/red-teaming/godmode/scripts/load_godmode.py — fix one occurrence (line 20)
  • optional-skills/productivity/memento-flashcards/scripts/memento_cards.py — fix _HERMES_HOME assignment (line 18)
  • optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py — fix --target argparse default (line 2963)

Not changed (already have correct ImportError shims):

  • plugins/disk-cleanup/disk_cleanup.py
  • skills/productivity/google-workspace/scripts/_hermes_home.py

How to Test

  1. Set a custom home and confirm all paths resolve under it:
    export HERMES_HOME=/tmp/test-hermes-home
    mkdir -p $HERMES_HOME
    hermes chat --oneshot "hello"
    ls /tmp/test-hermes-home/   # should contain sessions/, state.db, etc.
    ls ~/.hermes/               # should have no new files from this run
  2. Run the test suite — conftest.py sets HERMES_HOME to a tmpdir per test, so a passing suite confirms no test leaks into ~/.hermes:
    pytest tests/ -q
  3. Profile isolation:
    export HERMES_HOME=/tmp/profile-test
    hermes -p myprofile chat --oneshot "hello"
    # State should appear under the profile path, not ~/.hermes

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: <!-- e.g. Ubuntu 24.04, macOS 15.2, Windows 11 -->

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Screenshots / Logs

N/A — mechanical path substitution. No behavior change for default single-user installs where HERMES_HOME is unset.

Changed files

  • hermes_cli/auth.py (modified, +1/-1)
  • hermes_cli/env_loader.py (modified, +2/-1)
  • hermes_cli/gateway.py (modified, +1/-1)
  • hermes_cli/main.py (modified, +1/-1)
  • hermes_cli/slack_cli.py (modified, +2/-1)
  • mcp_serve.py (modified, +3/-3)
  • optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py (modified, +1/-1)
  • optional-skills/productivity/memento-flashcards/scripts/memento_cards.py (modified, +1/-1)
  • plugins/hermes-achievements/dashboard/plugin_api.py (modified, +11/-3)
  • plugins/memory/honcho/client.py (modified, +2/-1)
  • scripts/discord-voice-doctor.py (modified, +8/-1)
  • scripts/profile-tui.py (modified, +10/-2)
  • skills/red-teaming/godmode/scripts/auto_jailbreak.py (modified, +2/-2)
  • skills/red-teaming/godmode/scripts/load_godmode.py (modified, +1/-1)
  • tools/mcp_oauth.py (modified, +1/-1)

Code Example

N/Athis is a code-level bug identified by static analysis. Reproducible by setting `HERMES_HOME=/tmp/test_home` and observing that the listed files still write to `~/.hermes`.

---



---

def get_hermes_home() -> Path:
    val = os.environ.get("HERMES_HOME", "").strip()
    return Path(val) if val else Path.home() / ".hermes"

---

from hermes_constants import get_hermes_home

hermes_home = get_hermes_home()
RAW_BUFFERClick to expand / collapse

Bug Description

3 production files hardcode Path.home() / ".hermes" instead of calling the canonical get_hermes_home() from hermes_constants. This silently ignores the HERMES_HOME environment variable, breaking:

  • Docker deployments — sessions, state, and channel data are written to ~/.hermes even when HERMES_HOME=/data (or any custom path) is set
  • Profile isolation (hermes -p <profile>) — the profile-specific HERMES_HOME is bypassed at these callsites, causing profile data to bleed into the default home
  • CI test isolationtests/conftest.py sets HERMES_HOME to a per-test tempdir; these files ignore it and read/write the real ~/.hermes, contaminating production state during tests

tests/conftest.py:10–12 explicitly documents this as a known bug class:

"Code using Path.home() / ".hermes" instead of the canonical get_hermes_home() is a bug to fix at the callsite."

get_hermes_home() is already imported correctly 239 times — these 23 callsites are the outliers.

Steps to Reproduce

Docker / custom HERMES_HOME:

  1. Run Hermes in Docker with HERMES_HOME=/data/hermes set
  2. Observe that sessions, state DB, and channel directory are still created under ~/.hermes/ instead of /data/hermes/

Profile isolation:

  1. Run hermes -p myprofile chat
  2. Observe that env_loader, auth, and gateway read from ~/.hermes instead of the profile-isolated home

CI test isolation:

  1. Run the test suite — conftest.py sets HERMES_HOME to a tmpdir
  2. Files listed below bypass this and read/write real ~/.hermes during test runs

Expected Behavior

All path resolution goes through get_hermes_home(), which returns Path(HERMES_HOME) when the env var is set, and falls back to Path.home() / ".hermes" only when it is not. Custom deployments, profile isolation, and test isolation all work correctly.

Actual Behavior

The 23 callsites below ignore HERMES_HOME and always resolve to Path.home() / ".hermes":

FileLines
mcp_serve.py68, 105, 349
hermes_cli/main.py980
hermes_cli/env_loader.py157
hermes_cli/auth.py735
hermes_cli/gateway.py1512
hermes_cli/slack_cli.py131
plugins/hermes-achievements/dashboard/plugin_api.py138, 142, 146
plugins/memory/honcho/client.py76
plugins/disk-cleanup/disk_cleanup.py38
tools/mcp_oauth.py104
scripts/discord-voice-doctor.py22
scripts/profile-tui.py40, 41
skills/productivity/google-workspace/scripts/_hermes_home.py32
skills/red-teaming/godmode/scripts/auto_jailbreak.py38, 60
skills/red-teaming/godmode/scripts/load_godmode.py20
optional-skills/productivity/memento-flashcards/scripts/memento_cards.py18
optional-skills/migration/openclaw-migration/scripts/openclaw_to_hermes.py2963

Note: hermes_cli/auth.py:735 may be intentional — it compares against the real auth store specifically to prevent test runs from corrupting production auth. This line should be verified before changing.

Affected Component

CLI (interactive chat), Gateway (Telegram/Discord/Slack/WhatsApp), Configuration (config.yaml, .env, hermes setup), Skills (skill loading, skill hub, skill guard)

Messaging Platform (if gateway-related)

N/A (CLI only)

Debug Report

N/A — this is a code-level bug identified by static analysis. Reproducible by setting `HERMES_HOME=/tmp/test_home` and observing that the listed files still write to `~/.hermes`.

Operating System

All platforms (Linux, macOS, Windows)

Python Version

No response

Hermes Version

No response

Additional Logs / Traceback (optional)

Root Cause Analysis (optional)

get_hermes_home() in hermes_constants.py is the single source of truth:

def get_hermes_home() -> Path:
    val = os.environ.get("HERMES_HOME", "").strip()
    return Path(val) if val else Path.home() / ".hermes"

The 23 callsites bypass this by inlining Path.home() / ".hermes" directly (or via equivalent os.environ.get("HERMES_HOME", Path.home() / ".hermes") patterns that miss whitespace stripping and future logic). The tests/conftest.py test harness explicitly isolates HERMES_HOME per test, making these callsites a source of test pollution and confirming the bug.

Proposed Fix (optional)

Replace each occurrence with:

from hermes_constants import get_hermes_home

hermes_home = get_hermes_home()

This is a purely mechanical change with no logic change for default installations (where HERMES_HOME is unset). The fix restores correct behavior for Docker, profile isolation, and CI.

hermes_cli/auth.py:735 should be reviewed separately — if it is deliberately reading the real home to guard production auth during tests, it should be annotated with a comment explaining the intent rather than blindly replaced.

Are you willing to submit a PR for this?

  • I'd like to fix this myself and submit a PR

extent analysis

TL;DR

Replace hardcoded Path.home() / ".hermes" with a call to get_hermes_home() from hermes_constants to fix the issue.

Guidance

  • Identify the 23 callsites listed in the issue and replace the hardcoded path with get_hermes_home().
  • Verify that hermes_cli/auth.py:735 is not intentionally bypassing get_hermes_home() for security reasons before making changes.
  • Test the changes with different HERMES_HOME settings to ensure correct behavior.
  • Consider adding a comment to hermes_cli/auth.py:735 if it is intentionally reading the real home to guard production auth during tests.

Example

from hermes_constants import get_hermes_home

hermes_home = get_hermes_home()

Notes

The proposed fix is a mechanical change that restores correct behavior for Docker, profile isolation, and CI. However, it's essential to review hermes_cli/auth.py:735 separately to ensure it's not intentionally bypassing get_hermes_home() for security reasons.

Recommendation

Apply the workaround by replacing the hardcoded paths with a call to get_hermes_home(), as it fixes the issue without introducing any new logic changes.

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]: Path.home() / ".hermes" bypasses get_hermes_home() in 23 production files [1 pull requests, 1 participants]