Skip to content

fix(terminal): isolate ambient bridge and bind launch scope under multiplexing (#107422) - #107714

Open
JoaoMarcos44 wants to merge 1 commit into
NousResearch:mainfrom
JoaoMarcos44:fix/107422-multiplex-terminal-bridge-canonical
Open

JoaoMarcos44 wants to merge 1 commit into
NousResearch:mainfrom
JoaoMarcos44:fix/107422-multiplex-terminal-bridge-canonical

Conversation

@JoaoMarcos44

Copy link
Copy Markdown

Summary

Fixes #107422.

In multiplexed dashboard mode (app-global remote mode), when a secondary profile's turn ran without an active terminal scope, tools/terminal_tool._ensure_terminal_env_bridged() latched that secondary profile's terminal.* configuration into process-global os.environ and set _terminal_config_bridge_attempted = True. Consequently, subsequent tool calls under the primary/launch profile inherited the secondary profile's docker policy (e.g. TERMINAL_DOCKER_IMAGE, TERMINAL_DOCKER_VOLUMES, and backend type), resulting in mislabeled containers and cross-profile configuration leaks (a residual issue following #68559).


Root Cause Analysis

  1. Unscoped Bridge Poisoning:
    _ensure_terminal_env_bridged() in tools/terminal_tool.py checked only whether get_terminal_scope() is not None. It did not check whether get_hermes_home_override() was set. When an unscoped secondary profile call triggered the bridge, it read the secondary profile's config.yaml and latched those values into os.environ, also marking _terminal_config_bridge_attempted = True. This poisoned all subsequent primary-profile calls.

  2. Scope Propagation Across Asynchronous Worker Boundaries:
    _spawn_side_agent() in tui_gateway/methods_prompt.py spawns background daemon threads to execute side tasks (such as prompt.background and preview.restart). ContextVars do not automatically propagate across thread boundaries, causing background turns to lose their profile's terminal policy and secret scope unless explicitly bound in the worker.

  3. Eager Resume & Branch Build Scope Isolation:
    In tui_gateway/methods_session.py, _profile_build_scope() only bound HERMES_HOME and secret scope. Eager-resume and branch builds run inside this scope, so without an installed terminal scope, terminal policy probing could leak or latch defaults.

  4. Launch Profile Scope & Ambient Environment Preservation:
    Under multiplexing, once secondary profiles are served (_served_profile_homes is non-empty), launch-profile turns need an authoritative terminal scope to prevent falling back to process os.environ. However, if the launch profile's config.yaml omits a terminal section, naive scope construction would overwrite ambient process environment variables (such as TERMINAL_ENV=ssh set at launch). A dedicated build_launch_terminal_scope() was needed to preserve ambient process environment while respecting explicit launch config overrides.


Key Changes

  1. tools/terminal_tool.py:

    • Added an early return in _ensure_terminal_env_bridged() if get_hermes_home_override() is not None before touching os.environ or setting _terminal_config_bridge_attempted = True. Secondary profile operations never latch into process-global environment.
  2. tools/terminal_scope.py:

    • Added keyword-only ambient_env parameter to build_profile_terminal_scope().
    • Added build_launch_terminal_scope() and install_launch_terminal_scope(), which project ambient os.environ under TERMINAL_* keys and apply explicit launch config.yaml overrides.
  3. tui_gateway/prompt_turn.py:

    • In _prepare_turn_input(), when multiplexing is active (_served_profile_homes is non-empty), bind install_launch_terminal_scope() for launch profile turns.
  4. tui_gateway/methods_session.py:

    • Updated _profile_build_scope() to bind and reset install_profile_terminal_scope() alongside home and secret scopes.
  5. tui_gateway/methods_prompt.py:

    • Updated _spawn_side_agent() to bind and reset install_profile_terminal_scope (or install_launch_terminal_scope if multiplexed) on daemon worker threads.
  6. tui_gateway/server.py:

    • Cleaned up duplicate docstring comments in _profile_scoped.
  7. Tests:

    • tests/tools/test_terminal_env_bridge.py: Added test_secondary_home_override_does_not_latch_ambient_env validating that unscoped calls under secondary home override do not mutate os.environ or mark _terminal_config_bridge_attempted.
    • tests/tools/test_terminal_scope_multiplex.py: Added tests for ambient preservation on launch scope, multiplexed turn scope binding, _profile_build_scope terminal binding, and thread-boundary propagation in _spawn_side_agent.

Verification Evidence

  • tests/tools/test_terminal_env_bridge.py: 10 passed
  • tests/tools/test_terminal_scope_multiplex.py: 14 passed
  • Combined suite: 24 passed in 5.89s with 100% success rate.

…tiplexing (NousResearch#107422)

Multiplexed dashboard (app-global remote mode) previously allowed
_ensure_terminal_env_bridged() to execute while a secondary profile's
HERMES_HOME override was active. Because the bridge writes to process-global
os.environ and latches _terminal_config_bridge_attempted=True, subsequent
turns under the launch/primary profile inherited the secondary profile's
docker policy, spawning containers with mismatched docker tags and mounts.

Root Cause:
- _ensure_terminal_env_bridged() only checked get_terminal_scope() is not None,
  ignoring get_hermes_home_override(). When an unscoped secondary profile call
  triggered the bridge, it wrote the secondary profile's terminal configuration
  into os.environ and latched the one-shot bridge attempt.

Key Fixes:
1. tools/terminal_tool.py:
   - Early-return in _ensure_terminal_env_bridged() if get_hermes_home_override() is
     not None, ensuring secondary profile environments never mutate process-global
     os.environ or poison the one-shot bridge flag.
2. tools/terminal_scope.py:
   - Add optional ambient_env keyword to build_profile_terminal_scope().
   - Introduce build_launch_terminal_scope() and install_launch_terminal_scope()
     to preserve launch-time ambient process environment while overriding explicit
     keys from launch config.yaml.
3. tui_gateway/prompt_turn.py:
   - When multiplexing is active (_served_profile_homes is non-empty), bind
     install_launch_terminal_scope() on launch-profile turns during _prepare_turn_input().
4. tui_gateway/methods_session.py:
   - Bind install_profile_terminal_scope() in _profile_build_scope() for eager-resume
     and branch builds.
5. tui_gateway/methods_prompt.py:
   - Propagate and reset profile terminal scope across background thread boundaries
     in _spawn_side_agent() workers.
6. Cleaned duplicate docstring paragraphs in tui_gateway/server.py.
7. Regression Tests:
   - Added test_secondary_home_override_does_not_latch_ambient_env in
     tests/tools/test_terminal_env_bridge.py.
   - Added comprehensive tests for launch scope ambient preservation, multiplexed
     turn preparation, profile build scope, and side-agent worker propagation in
     tests/tools/test_terminal_scope_multiplex.py.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists tool/terminal Terminal execution and process management comp/tui Terminal UI (ui-tui/ + tui_gateway/) comp/tools Tool registry, model_tools, toolsets area/profiles Multi-profile isolation, HERMES_HOME scoping sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Sep 10, 2026
@alt-glitch

Copy link
Copy Markdown

This was generated by AI during triage.

Related: competing fix for #107422 alongside earlier open #107442. Both PRs carry the same core change (_ensure_terminal_env_bridged() returns early when a HERMES_HOME override is active); this PR additionally binds terminal/secret scope in _spawn_side_agent worker threads, _profile_build_scope, and the launch profile. Flagging so a maintainer can pick the narrow fix or the broader one rather than merging both.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/profiles Multi-profile isolation, HERMES_HOME scoping comp/tools Tool registry, model_tools, toolsets comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state tool/terminal Terminal execution and process management type/bug Something isn't working

Projects

None yet

2 participants