fix(kanban): strip stale session routing from dispatched worker env

A long-lived gateway can have platform routing (HERMES_SESSION_* /
HERMES_CRON_AUTO_DELIVER_*) mirrored in os.environ from a previous turn.
_default_spawn() copied that process environment verbatim into detached
kanban workers, so a worker calling kanban_create treated the inherited
chat/topic as its origin and auto-subscribed the child task — the task's
terminal notification then woke an unrelated chat.

Strip every registered session-context routing key from the worker env
unconditionally (the dispatcher is detached from every conversation);
board, workspace, task, branch, profile, model, and credential
propagation are unchanged.

Salvaged from PR #69181 (both commits squashed; the PR's second commit
fixed the first's engagement-latch assumption).
This commit is contained in:
trymhaak 2026-07-26 13:43:45 -07:00 committed by Teknium
parent c93ed07459
commit 148497f6d6
2 changed files with 19 additions and 5 deletions

View file

@ -8815,6 +8815,12 @@ def _default_spawn(
prompt = f"work kanban task {task.id}"
env = dict(os.environ)
# The dispatcher is detached from every conversation. Its worker must never
# inherit routing mirrored by a previous gateway turn, even before the first
# session binds ContextVars in this process.
from gateway.session_context import _VAR_MAP
for key in _VAR_MAP:
env.pop(key, None)
# Inject HERMES_HOME so the worker reads the profile-scoped config.yaml
# (fallback_providers, toolsets, agent settings, etc.) instead of the root

View file

@ -3096,17 +3096,23 @@ class TestSharedBoardPaths:
assert kb.kanban_db_path() == default_home / "kanban.db"
assert kb.workspaces_root() == default_home / "kanban" / "workspaces"
def test_dispatcher_spawn_injects_kanban_db_and_workspaces_root(
def test_dispatcher_spawn_injects_kanban_paths_without_stale_session(
self, tmp_path, monkeypatch
):
# The dispatcher's `_default_spawn` must inject HERMES_KANBAN_DB
# and HERMES_KANBAN_WORKSPACES_ROOT into the worker env so the
# worker converges on the dispatcher's paths even when the
# `-p <profile>` flag rewrites HERMES_HOME.
# The dispatcher must pin board paths while stripping any unrelated
# HERMES_SESSION_* identity inherited from the long-lived gateway.
default_home = tmp_path / ".hermes"
default_home.mkdir()
self._set_home(monkeypatch, tmp_path, default_home)
from gateway import session_context as sc
# A dispatcher can launch before the gateway binds its first session.
monkeypatch.setattr(sc, "_session_context_engaged", False)
sc.reset_session_vars()
for key in sc._VAR_MAP:
monkeypatch.setenv(key, "stale-routing-value")
captured = {}
class _FakePopen:
@ -3144,6 +3150,8 @@ class TestSharedBoardPaths:
)
assert env["HERMES_KANBAN_TASK"] == "t_dispatch_env"
assert env["HERMES_KANBAN_BRANCH"] == "wt/t_dispatch_env"
for key in sc._VAR_MAP:
assert key not in env
# ---------------------------------------------------------------------------