Skip to content

fix(constants): warn once when get_hermes_home() falls back under an active profile#18746

Merged
teknium1 merged 1 commit into
mainfrom
hermes/hermes-3d89efe9
May 2, 2026
Merged

fix(constants): warn once when get_hermes_home() falls back under an active profile#18746
teknium1 merged 1 commit into
mainfrom
hermes/hermes-3d89efe9

Conversation

@teknium1

@teknium1 teknium1 commented May 2, 2026

Copy link
Copy Markdown
Contributor

Makes the ~/.hermes fallback visible when a non-default profile is active. Surfaces cross-profile data contamination (#18594) without breaking the 30+ module-level callers that import get_hermes_home() at load time.

What changed

  • hermes_constants.py: when HERMES_HOME is unset and ~/.hermes/active_profile names a non-default profile, write a one-shot warning to stderr and continue returning ~/.hermes as before.
  • tests/test_hermes_home_profile_warning.py: 6 test cases (classic mode, default profile, named profile, env-set-wins, unreadable active_profile, empty active_profile).

Why not raise (superseding #18600)

The original PR proposal raised ValueError from get_hermes_home() when a named profile was active without HERMES_HOME set. 30+ files call this at module-import scope (run_agent.py, gateway/run.py, cron/scheduler.py, hermes_cli/doctor.py, tools/skills_tool.py, acp_adapter/entry.py, etc.) — raising there would brick imports in every cron tick, subagent, and IDE launcher where HERMES_HOME didn't propagate for any reason. That failure mode is worse than the silent bleed it fixes. POSIX subprocesses actually DO inherit parent env by default, so the real propagation paths (systemd unit Environment=, kanban dispatcher env=dict(os.environ), docker entrypoint) already work.

The stderr write bypasses logging because this function runs before logging is configured in many of the import-time callers, and going through the root logger double-emits on consoles that already have a StreamHandler.

Validation

  • scripts/run_tests.sh tests/test_hermes_home_profile_warning.py tests/hermes_cli/test_profiles.py → 100 passed.
  • E2E: real HOME redirect + active_profile=coder + HERMES_HOME unset. 10 calls to get_hermes_home() → exactly 1 warning emitted to stderr, fallback path still returned, module imports of hermes_cli.gateway and cron.scheduler succeed.

Closes #18594. Credit to @liuhao1024 for surfacing the silent-fallback case in #18600.

…active profile

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 merged commit 699b367 into main May 2, 2026
9 of 10 checks passed
@teknium1 teknium1 deleted the hermes/hermes-3d89efe9 branch May 2, 2026 08:49
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/cli CLI entry point, hermes_cli/, setup wizard area/config Config system, migrations, profiles labels May 2, 2026
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 P2 Medium — degraded but workaround exists 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

2 participants