Skip to content

fix(tui): isolate background notifications by UI session - #61719

Open
soria-clawd-bot wants to merge 1 commit into
NousResearch:mainfrom
soria-clawd-bot:fix/process-completion-session-routing
Open

fix(tui): isolate background notifications by UI session#61719
soria-clawd-bot wants to merge 1 commit into
NousResearch:mainfrom
soria-clawd-bot:fix/process-completion-session-routing

Conversation

@soria-clawd-bot

Copy link
Copy Markdown

What does this PR do?

Prevents Desktop/WebUI background-process completion and watch notifications from contaminating unrelated conversations.

A delegated child can start terminal(background=true, notify_on_complete=true) and exit before the process finishes. The process keeps the child execution session_key, but that child is no longer a live UI session. The TUI/WebUI server historically adopted the orphaned event into whichever session poller dequeued the process-global queue first.

This patch keeps execution isolation and return routing separate:

  • preserves the child session_key for process/tool isolation;
  • captures the commissioning tab as origin_ui_session_id;
  • persists that return address in the process checkpoint and completion/watch events;
  • propagates only that UI return address across the delegated-child executor boundary;
  • requires positive ownership before injecting conversation-bearing process notifications; and
  • keeps originless, process-global watch-overflow summaries out of conversational WebUI delivery.

This supersedes the direct-TUI portions of #35672 and #54785: both route by session_key but do not preserve the exact UI origin across delegated executors, persist that return address, or gate every post-turn event type. It is complementary to #16455, which guards stale physical sessions in the messaging gateway path.

Related Issue

Fixes #42674

Related: #35652, #16455, #35672, #54785

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • 🔒 Security fix
  • ✅ Tests (adding or improving test coverage)

Changes Made

  • tools/process_registry.py: store/checkpoint origin_ui_session_id, include it on completion/watch events, apply the ownership callback to every post-turn event type, and normalize malformed/future event types without crashing.
  • tools/terminal_tool.py: capture the live UI origin when starting notified background processes.
  • gateway/session_context.py + tools/delegate_tool.py: bind that origin across both delegated-child executor boundaries without replacing the child's isolation session_key.
  • tui_gateway/server.py: route by exact origin when present; fail closed for ownerless completion/watch events, including global overflow summaries.
  • Tests cover direct spawn capture, checkpoint writes and legacy reads, completion/watch propagation, exact-tab ownership, ownerless drops, two-session overflow isolation, malformed/future event types, every post-turn event family, positional-constructor compatibility, and the real outer/inner delegated executor path.

How to Test

  1. Start a delegated child from WebUI session A; have it launch a notified background process that finishes after the child exits.
  2. Keep unrelated WebUI session B active and verify B never receives the completion/watch notification.
  3. Verify session A receives the notification while live; if A is gone and no durable owner remains, verify the unsolicited event is dropped while process output remains available through the process registry.

Local verification on Ubuntu/Linux:

  • Touched routing matrix: 530 passed across the six modified regression files.
  • Full repository suite: 39,988 passed. The two credential-pool failures and the 310-second doctor timeout reproduce on clean origin/main; the web-server timing exception passed 3/3 when rerun alone after the saturated 32-way run.
  • Ruff passed on every changed Python file.
  • compileall, a diff secret/injection heuristic scan, and git diff --check passed.

Checklist

Code

Documentation & Housekeeping

  • Documentation update: N/A; no user-facing config/API change
  • cli-config.yaml.example: N/A; no config keys changed
  • CONTRIBUTING.md / AGENTS.md: N/A; no workflow change
  • Cross-platform impact considered: stdlib ContextVar and dataclass fields only
  • Tool descriptions/schemas: N/A; no tool interface change

Screenshots / Logs

The outer/inner executor regression fails without the binding (worker_origin == "") and passes with the patch ("parent-tab"), with the resulting process completion event carrying origin_ui_session_id="parent-tab".

The two-session overflow regression fails without the expanded fail-closed set (the first poller starts an unrelated agent turn) and passes with the patch (no emit, no turn, queue drained).

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/tui Terminal UI (ui-tui/ + tui_gateway/) tool/delegate Subagent delegation tool/terminal Terminal execution and process management sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state labels Jul 10, 2026
@soria-clawd-bot
soria-clawd-bot force-pushed the fix/process-completion-session-routing branch from d4c33b6 to 54dbd8b Compare July 10, 2026 03:36

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for tracing the child-executor and process-queue paths. The core premise is confirmed on current origin/main: tools/process_registry.py:1085-1093 emits non-delegation completion events with only session_key, while tui_gateway/server.py:8677-8729 permits orphaned completion/watch events through after applying its fail-closed gate only to async_delegation.

Problems

  • tools/terminal_tool.py:2365 captures origin_ui_session_id only when notify_on_complete or watch_patterns is enabled. Live process output is emitted for every background process (tools/process_registry.py:217-225, 951-959 on current main), while this PR's terminal owner routing requires positive ownership. A delegated plain background=true process will therefore have no exact UI owner and its agent.terminal.output chunks will be dropped.

Suggested changes

  • Capture the UI origin for every background spawn and add a delegated-child test for plain background live-output routing.

Automated hermes-sweeper review.

Comment thread tools/terminal_tool.py
# Delegated children replace session_key with their temporary child
# session, but inherit HERMES_UI_SESSION_ID from the parent chat.
origin_ui_session_id = ""
if notify_on_complete or watch_patterns:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This condition leaves origin_ui_session_id empty for plain background=true processes. ProcessRegistry streams every output chunk, and the new positive-ownership terminal router will drop a delegated child's stream without an exact origin. Capture the UI origin for all background spawns and add the corresponding regression test.

@ayushnangia

Copy link
Copy Markdown
Contributor

Status map for maintainers: the ownership-routing core of this PR landed on main via merged 54d0948d3 ("route post-turn completions by owner", refs #54785) — drain_notifications now has the positive-proof owner filter and origin_ui_session_id event routing this PR proposed. The one gap the sweeper review flagged here (UI origin captured only when notify/watch is set → plain background=true live output has no owner and is dropped) remained on main and is now fixed in #73351, which credits this PR. With both landed, this branch's remaining diff is superseded — suggest closing once #73351 is reviewed.

ayushnangia added a commit to ayushnangia/hermes-agent that referenced this pull request Aug 6, 2026
… owner (NousResearch#61719 residual)

Merged 54d0948 landed owner-routing for post-turn completions, but the
sweeper's flagged gap on NousResearch#61719 survived: terminal_tool captured routing
metadata only when notify_on_complete/watch_patterns were set, while live
agent.terminal.output chunks are emitted for EVERY background process.
_owner_sid_for_process matched only by session_key, so a delegated child's
process (subagent-internal key, never a live TUI session) emitted its live
output with sid "" — dropped by write_json.

- ProcessSession.origin_ui_session_id: spawn-time UI owner, persisted in
  the crash checkpoint like the watcher metadata
- terminal_tool: capture HERMES_UI_SESSION_ID for every background spawn
  (same session-env pattern as desktop_ui.py:39 / delegate_tool.py:3191)
- _owner_sid_for_process: prefer the recorded origin while that window is
  live; legacy session_key equality remains the fallback

Salvages the remaining scope of NousResearch#61719 (bot-authored, sweeper problem
unaddressed).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/sessions Session lifecycle, resume, persistence, history comp/tui Terminal UI (ui-tui/ + tui_gateway/) P2 Medium — degraded but workaround exists sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-session-state Sweeper risk: may lose/corrupt/mis-associate session or context state tool/delegate Subagent delegation tool/terminal Terminal execution and process management type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Background process notify_on_complete leaks into wrong TUI session (cross-session bleed)

4 participants