Skip to content

fix(env): multi-profile serve no longer leaks a routed profile's terminal backend into the shared process env (#102769, salvage #97014) - #103625

Merged
kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:salvage/97014-terminal-bridge-process-home
Sep 5, 2026
Merged

kshitijk4poor merged 1 commit into
NousResearch:mainfrom
kshitijk4poor:salvage/97014-terminal-bridge-process-home

Conversation

@kshitijk4poor

Copy link
Copy Markdown

In a multi-profile hermes serve (Desktop backend with the in-process multiplex cron ticker), a routed profile's terminal.* config no longer leaks into the shared os.environ, so the launch profile's next unscoped turn stays on its own terminal backend instead of running inside another profile's Docker container with that profile's volumes mounted.

Salvage of #97014 by @Bergmann89 (single commit cherry-picked unchanged, authorship preserved). Fixes #102769, #96992. Supersedes duplicate #103512.

Root cause

env_loader._reapply_terminal_config_bridge guards "only bridge config → env for the process's own profile" by comparing against _process_hermes_home(), which resolved through get_hermes_home() — a resolver that FOLLOWS the context-local set_hermes_home_override the cron ticker installs per profile. Under the override the guard passed for the secondary profile and apply_terminal_config_to_env() wrote its TERMINAL_ENV=docker / TERMINAL_DOCKER_VOLUMES=… into the process-global environment.

Change

  • _process_hermes_home() delegates to hermes_constants.get_process_hermes_home(), the override-immune resolver that already exists for exactly this. Its other caller (_load_secrets_config shared-cache reuse) now also refuses the shared cache under an override — the safe direction.
  • 2 invariant tests (tests/hermes_cli/test_terminal_bridge_profile_scope.py), red on origin/main.

Validation

Check Result
Live repro: root profile backend: local, secondary profile backend: docker + volumes; scope to the secondary via set_hermes_home_override and load_hermes_dotenv(hermes_home=<secondary>) (what the cron tick does) main: shared env gets TERMINAL_ENV=docker, TERMINAL_DOCKER_VOLUMES=["scout-session:/session"]; branch: both stay unset
scripts/run_tests.sh env_loader / terminal-bridge / scheduler_provider suites 75 passed
ruff / compat pointers clean

In a multiplex dashboard (one process serving every profile on the host),
a secondary profile's terminal.backend could leak into the shared
os.environ and silently override the launch profile's terminal backend.
A profile configured for terminal.backend: ssh would find itself running
every command locally because a sibling profile with backend: local had
polluted the shared environment.

The tell is an asymmetric env state on the launch session:

    TERMINAL_ENV=local            # leaked from a sibling profile
    TERMINAL_SSH_HOST=10.10.0.103 # still the launch profile's, untouched

A restart does not help: as soon as the sibling profile is touched again
(a turn, a cron tick), the shared env is re-poisoned.

Root cause: env_loader._reapply_terminal_config_bridge() guards "only
re-bridge config into the shared os.environ when this load is for the
process's own profile" via _process_hermes_home(). That helper delegated
to get_hermes_home(), which follows the context-local
set_hermes_home_override a per-request task installs. When a per-turn
handler is scoped to a secondary profile and triggers a dotenv reload,
both sides of the comparison resolve to that secondary profile, the guard
passes, and the bridge writes the wrong profile's terminal.backend into
the shared environment. Because the secondary profile's config carries no
TERMINAL_SSH_* keys, only TERMINAL_ENV is overwritten, producing the
asymmetric state above.

The helper was introduced for the config-cache key (where following the
active home is correct) and later reused for the terminal bridge guard,
where the override-following semantics are wrong.

Fix: delegate _process_hermes_home() to get_process_hermes_home(), the
override-immune resolver built for exactly this. It reflects the scope the
process was launched under, ignoring per-task overrides. Both call sites
(the bridge guard and the secrets-cache reuse check) want the true process
home. Single-profile / CLI behaviour is unchanged: with no active
override the two resolvers are identical.

Complements the terminal_tool._active_environments session-key fix: that
scopes the per-session environment cache; this keeps the shared os.environ
the cache reads TERMINAL_ENV from uncontaminated in the first place.

Adds tests/hermes_cli/test_terminal_bridge_profile_scope.py covering both
the override-immune resolver and the no-leak reload behaviour.
@kshitijk4poor
kshitijk4poor enabled auto-merge (rebase) September 5, 2026 11:26
@kshitijk4poor
kshitijk4poor merged commit 2e24e06 into NousResearch:main Sep 5, 2026
37 checks passed
@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/profiles Multi-profile isolation, HERMES_HOME scoping area/config Config system, migrations, profiles labels Sep 5, 2026
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 area/profiles Multi-profile isolation, HERMES_HOME scoping comp/cli CLI entry point, hermes_cli/, setup wizard P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

3 participants