fix(tools/tui): stop multiplex ambient TERMINAL_* latch from poisoning launch profile (#107422) - #107442
fix(tools/tui): stop multiplex ambient TERMINAL_* latch from poisoning launch profile (#107422)#107442infinitycrew39 wants to merge 2 commits into
Conversation
…ride A multiplexed dashboard can call _ensure_terminal_env_bridged while a secondary profile's HERMES_HOME override is active. The one-shot latch then wrote that profile's docker policy into process-global os.environ and poisoned later unscoped launch-profile tool calls (NousResearch#107422). Skip the ambient bridge whenever a context-local home override is set — ambient env is launch-profile authority only; routed profiles must use terminal_scope (same rule as env_loader._reapply_terminal_config_bridge).
After any secondary profile home is served, launch-profile turns used to stay unscoped and fall back to ambient os.environ. Bind the launch home's own terminal policy in that case so a poisoned ambient bridge can never become the launch turn's authority (NousResearch#107422 residual of NousResearch#68559).
b1e659e to
f81147c
Compare
ehz0ah
left a comment
There was a problem hiding this comment.
I found four blocking issues at f81147c1e5d283837e5e27f4da79710da7025235. The profile-scoped direction is safer than mutating shared process environment state, but the current patch does not yet preserve a complete terminal policy for every profile.
-
Secondary side-agent workers still use the launch profile's terminal policy.
_spawn_side_agent()scopes onlyHERMES_HOME. With the new bridge guard, a secondary profile configured for Docker reads the launch process's ambientTERMINAL_ENV=local. This affectsprompt.background,prompt.btw, andpreview.restart. A behavioral countertest configured the secondary profile for Docker and observedlocalinside the worker. Please bind and reset that profile's terminal scope for the full worker lifetime. -
Secondary eager-resume and branch builds also omit terminal scope.
_profile_build_scope()binds home and secrets only, and both_resume_eager()and_build_branch_agent()construct agents inside that scope. Tool availability discovery can read terminal configuration during construction, so these paths can freeze the launch profile's backend into a secondary agent's tool schema. Please include terminal scope in the common profile build scope. -
The launch-profile scope discards valid environment-only configuration once multiplexing starts. If the dashboard starts with
TERMINAL_ENV=sshandTERMINAL_SSH_HOST=example.test, but the launch home has no terminal config file,_prepare_turn_input()installs a file-backed scope that resolves tolocalwith an empty host. Legacy single-profile behavior therefore changes after a second profile is served. The launch scope needs to preserve the launch profile's effective ambient policy. -
The new launch-profile test does not exercise the changed runtime branch and fails when run by itself. It calls
install_profile_terminal_scope()directly instead of_prepare_turn_input()with multiplexing active. The autouse fixture already createstmp_path / ".hermes", solaunch_home.mkdir()raisesFileExistsErrorbefore the assertions. The same direct scope behavior also passes on the base commit. Please replace it with a regression that enters the real launch-turn path and fails before the fix.
Validation on the exact head: the directly relevant suite passed 27 tests, the broader gateway set passed 58 tests, Ruff passed, compatibility and Windows-footgun checks completed, and the merge into current upstream main was conflict-free. Three focused behavioral countertests reproduced findings 1 through 3.
|
Landed on |
…own terminal scope Under multiplexing tools/terminal_tool.py no longer bridges a profile's terminal.* into os.environ while a home override is bound (#108440), so any secondary-profile entrypoint that binds only the home (+ secrets) leaves terminal_tool on the launch process's ambient TERMINAL_*: a secondary with `terminal.backend: docker` ran its prompt.background / prompt.btw / preview.restart side agents, its eager resume and its session.branch build on the launch `local` backend. _spawn_side_agent and _profile_build_scope now enter _session_profile_runtime_scope, the same home -> secrets -> terminal composition a prompt turn binds (and gateway/run.py::_profile_runtime_scope mirrors). The terminal scope is installed for the whole worker/build lifetime and reset on success and on exception; a malformed secondary config still yields the fail-closed refusal scope. No ambient os.environ write is restored. Refs #108440 review (andrexibiza), #107442 (ehz0ah).
…olicy once multiplexing is active 606903b made launch-profile turns bind a file-backed terminal scope as soon as any secondary home is served, so a poisoned ambient bridge can never be the launch turn's authority. That scope is rebuilt from defaults + <home>/.env + config.yaml only, which drops the launch process's legitimate env-only policy: TERMINAL_ENV=ssh TERMINAL_SSH_HOST=example.test with a `{}` config.yaml became backend=local, ssh_host='' the moment a second profile was served. tui_gateway/launch_terminal_policy.py freezes the process TERMINAL_* once, in _profile_home right before the first secondary home is registered as served — the last moment ambient env is provably the launch profile's own. build_profile_terminal_scope takes that snapshot as a trusted env_overlay sitting where the process env sits in the standalone bridge (explicit YAML keys still win). Launch turns overlay the snapshot; ambient os.environ is never re-read after activation, so a later secondary write is still rejected, and the scope is reset after the turn as before. Refs #108440 review (andrexibiza), #107442 (ehz0ah).
Summary
upstream/main:_ensure_terminal_env_bridged()'s one-shot latch can write a secondary profile'sterminal.*into process-globalos.environwhen a home override is active without a terminal scope; unscoped launch-profile tool calls then spawn that profile's docker image/volumes.HERMES_HOMEoverride is set (ambient env is launch-profile authority only; routed profiles useterminal_scope— same rule asenv_loader._reapply_terminal_config_bridge)._served_profile_homes, bind the launch home's own terminal scope on launch turns so they never fall back to a poisoned ambient env.Related open approach #94206 re-bridges per home into
os.environ; this PR follows the #68559 fail-closed scope model instead (no cross-profile writes into shared process env).Fixes #107422
Test plan
pytest tests/tools/test_terminal_env_bridge.py -q(includes new secondary-override latch regression)pytest tests/tools/test_terminal_scope_multiplex.py::test_launch_turn_binds_terminal_scope_once_multiplexing_is_active -qterminal.backend: local; secondary docker + volumes; open secondary session then run a primary file/terminal tool — must stay on host / primary policy, never createhermes-<primary>with secondary image/volumes