Skip to content

fix(constants): raise ValueError when HERMES_HOME unset in profile mode#18600

Closed
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:fix/issue-18594-profile-hermes-home-fallback
Closed

fix(constants): raise ValueError when HERMES_HOME unset in profile mode#18600
liuhao1024 wants to merge 1 commit into
NousResearch:mainfrom
liuhao1024:fix/issue-18594-profile-hermes-home-fallback

Conversation

@liuhao1024

Copy link
Copy Markdown
Contributor

Summary

When a non-default profile is active (~/.hermes/active_profile exists with a non-default value) but HERMES_HOME is not inherited by a sub-process, get_hermes_home() now raises ValueError instead of silently returning ~/.hermes.

Problem: In profile mode, the CLI launcher sets HERMES_HOME=<root>/profiles/<name> before spawning sub-processes (cron jobs, skills, sub-agents). These sub-processes do not inherit the parent's environment variables. When get_hermes_home() is called without HERMES_HOME, it falls back to ~/.hermes — the default profile — causing silent cross-profile data corruption.

Real incident (May 1 2026): A holographic-memory cron job ran SQL queries against ~/.hermes/memory_store.db instead of the profile's database because HERMES_HOME was not inherited, contaminating the default profile with personal data.

Fix

Reads ~/.hermes/active_profile when HERMES_HOME is unset. If a non-default profile is active, raises ValueError with a clear message directing the operator to set HERMES_HOME. Classic mode (no active_profile file or default value) is unaffected — the fallback to ~/.hermes remains correct.

Test Plan

  • RED: test_profile_mode_raises_when_hermes_home_unset fails on current main (DID NOT RAISE)
  • GREEN: All 5 new tests pass after fix
  • Regression: tests/hermes_cli/test_profiles.py — 94 passed
  • Regression: tests/test_subprocess_home_isolation.py — all passed
  • Regression: Full hermes_cli/ test suite — 0 new failures vs main

Diff Stats

  • hermes_constants.py: +23 lines (guard clause in get_hermes_home())
  • tests/hermes_cli/test_get_hermes_home_profile_fallback.py: +71 lines (5 test cases)

Closes #18594

When a non-default profile is active (active_profile file exists) but
HERMES_HOME is not inherited by a sub-process, get_hermes_home() now
raises ValueError instead of silently returning ~/.hermes. This prevents
cross-profile data corruption where cron jobs, skills, and sub-agents
write to the wrong profile's database.

Classic mode (no active_profile or 'default') is unaffected — the
fallback to ~/.hermes remains the correct behavior there.

Closes NousResearch#18594
@alt-glitch alt-glitch added type/bug Something isn't working P1 High — major feature broken, no workaround comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles labels May 2, 2026
teknium1 added a commit that referenced this pull request May 2, 2026
…active profile (#18746)

When HERMES_HOME is unset but ~/.hermes/active_profile names a non-default
profile, any data this process writes lands in the default profile — not the
one the operator expects. Before this change the fallback was silent, so
cross-profile contamination (#18594) was invisible until a user noticed
their memory/state ended up in the wrong place.

Now we emit a one-shot warning to stderr the first time this happens in
a process. No raise — there are 30+ module-level callers of get_hermes_home()
and raising from any of them would brick import. Behavior is otherwise
unchanged; subprocess spawners (systemd template, kanban dispatcher, docker
entrypoint) already propagate HERMES_HOME correctly.

Bypasses logging.getLogger() because this runs before logging is configured
in a significant fraction of callers (module import time).

Refs #18594. Credit to @liuhao1024 for surfacing the silent-fallback case
in PR #18600; we kept the diagnostic signal without the import-time raise.
@teknium1

teknium1 commented May 2, 2026

Copy link
Copy Markdown
Contributor

Thanks for catching this silent-fallback case — closed in favor of #18746 which takes a narrower approach.

Your PR raised ValueError from get_hermes_home(). On review that would brick 30+ module-level callers (run_agent.py, gateway/run.py, cron/scheduler.py, hermes_cli/doctor.py, tools/skills_tool.py, acp_adapter/entry.py, many more) whenever HERMES_HOME didn't propagate for any reason — which is a worse failure mode than the silent bleed it fixes. POSIX subprocesses actually inherit parent env by default and our real spawners (systemd unit Environment=, kanban dispatcher, docker entrypoint) already propagate HERMES_HOME.

#18746 keeps the diagnostic signal you identified as valuable (surfacing cross-profile fallback) by writing a one-shot warning to stderr, without the import-time raise. You're credited in the commit message.

#18746

@teknium1 teknium1 closed this May 2, 2026
Cyrene963 pushed a commit to Cyrene963/hermes-agent that referenced this pull request May 3, 2026
Community PRs applied:
- NousResearch#18596: Enable secret redaction by default (SECURITY)
- NousResearch#18650: Sanitize malformed tool messages + auto-recover on API 400
- NousResearch#18607: Emergency compression before max_iterations exhaustion
- NousResearch#18603: Compression fallback to main model on 413 rate limit
- NousResearch#18638: Pass threshold_percent on model switch
- NousResearch#18663: Strip extra_content from tool_calls for strict APIs
- NousResearch#18618: Forward explicit_api_key to OpenRouter
- NousResearch#18632: Show cache tokens in /insights breakdown
- NousResearch#18614: Add idempotency guard for patch duplicate loops
- NousResearch#18600: Raise ValueError when HERMES_HOME unset in profile mode
- NousResearch#18616: Allow ZWJ emoji in context files
- NousResearch#18582: Reload .env on /restart
- NousResearch#18547: Stabilize system prompt prefix for KV cache reuse
- NousResearch#18692: Strip FTS5 operators from session search truncation terms

Fix: Add order_by_last_active=True to list_sessions_rich call
(pre-existing commit 142b4bf code sync)
nickdlkk pushed a commit to nickdlkk/hermes-agent that referenced this pull request May 11, 2026
…active profile (NousResearch#18746)

When HERMES_HOME is unset but ~/.hermes/active_profile names a non-default
profile, any data this process writes lands in the default profile — not the
one the operator expects. Before this change the fallback was silent, so
cross-profile contamination (NousResearch#18594) was invisible until a user noticed
their memory/state ended up in the wrong place.

Now we emit a one-shot warning to stderr the first time this happens in
a process. No raise — there are 30+ module-level callers of get_hermes_home()
and raising from any of them would brick import. Behavior is otherwise
unchanged; subprocess spawners (systemd template, kanban dispatcher, docker
entrypoint) already propagate HERMES_HOME correctly.

Bypasses logging.getLogger() because this runs before logging is configured
in a significant fraction of callers (module import time).

Refs NousResearch#18594. Credit to @liuhao1024 for surfacing the silent-fallback case
in PR NousResearch#18600; we kept the diagnostic signal without the import-time raise.
jsboige pushed a commit to jsboige/hermes-agent that referenced this pull request May 14, 2026
…active profile (NousResearch#18746)

When HERMES_HOME is unset but ~/.hermes/active_profile names a non-default
profile, any data this process writes lands in the default profile — not the
one the operator expects. Before this change the fallback was silent, so
cross-profile contamination (NousResearch#18594) was invisible until a user noticed
their memory/state ended up in the wrong place.

Now we emit a one-shot warning to stderr the first time this happens in
a process. No raise — there are 30+ module-level callers of get_hermes_home()
and raising from any of them would brick import. Behavior is otherwise
unchanged; subprocess spawners (systemd template, kanban dispatcher, docker
entrypoint) already propagate HERMES_HOME correctly.

Bypasses logging.getLogger() because this runs before logging is configured
in a significant fraction of callers (module import time).

Refs NousResearch#18594. Credit to @liuhao1024 for surfacing the silent-fallback case
in PR NousResearch#18600; we kept the diagnostic signal without the import-time raise.
dannyJ848 pushed a commit to dannyJ848/hermes-agent that referenced this pull request May 17, 2026
…active profile (NousResearch#18746)

When HERMES_HOME is unset but ~/.hermes/active_profile names a non-default
profile, any data this process writes lands in the default profile — not the
one the operator expects. Before this change the fallback was silent, so
cross-profile contamination (NousResearch#18594) was invisible until a user noticed
their memory/state ended up in the wrong place.

Now we emit a one-shot warning to stderr the first time this happens in
a process. No raise — there are 30+ module-level callers of get_hermes_home()
and raising from any of them would brick import. Behavior is otherwise
unchanged; subprocess spawners (systemd template, kanban dispatcher, docker
entrypoint) already propagate HERMES_HOME correctly.

Bypasses logging.getLogger() because this runs before logging is configured
in a significant fraction of callers (module import time).

Refs NousResearch#18594. Credit to @liuhao1024 for surfacing the silent-fallback case
in PR NousResearch#18600; we kept the diagnostic signal without the import-time raise.
gweeteve pushed a commit to gweeteve/hermes-agent that referenced this pull request Jun 2, 2026
…active profile (NousResearch#18746)

When HERMES_HOME is unset but ~/.hermes/active_profile names a non-default
profile, any data this process writes lands in the default profile — not the
one the operator expects. Before this change the fallback was silent, so
cross-profile contamination (NousResearch#18594) was invisible until a user noticed
their memory/state ended up in the wrong place.

Now we emit a one-shot warning to stderr the first time this happens in
a process. No raise — there are 30+ module-level callers of get_hermes_home()
and raising from any of them would brick import. Behavior is otherwise
unchanged; subprocess spawners (systemd template, kanban dispatcher, docker
entrypoint) already propagate HERMES_HOME correctly.

Bypasses logging.getLogger() because this runs before logging is configured
in a significant fraction of callers (module import time).

Refs NousResearch#18594. Credit to @liuhao1024 for surfacing the silent-fallback case
in PR NousResearch#18600; we kept the diagnostic signal without the import-time raise.
Egavasyug pushed a commit to Egavasyug/hermes-agent that referenced this pull request Jun 10, 2026
…active profile (NousResearch#18746)

When HERMES_HOME is unset but ~/.hermes/active_profile names a non-default
profile, any data this process writes lands in the default profile — not the
one the operator expects. Before this change the fallback was silent, so
cross-profile contamination (NousResearch#18594) was invisible until a user noticed
their memory/state ended up in the wrong place.

Now we emit a one-shot warning to stderr the first time this happens in
a process. No raise — there are 30+ module-level callers of get_hermes_home()
and raising from any of them would brick import. Behavior is otherwise
unchanged; subprocess spawners (systemd template, kanban dispatcher, docker
entrypoint) already propagate HERMES_HOME correctly.

Bypasses logging.getLogger() because this runs before logging is configured
in a significant fraction of callers (module import time).

Refs NousResearch#18594. Credit to @liuhao1024 for surfacing the silent-fallback case
in PR NousResearch#18600; we kept the diagnostic signal without the import-time raise.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/cli CLI entry point, hermes_cli/, setup wizard P1 High — major feature broken, no workaround type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: get_hermes_home() silently falls back to ~/.hermes in profile mode and causes cross-profile data corruption

3 participants