Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions tests/test_tui_gateway_ws.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import io

from tui_gateway import server
from tui_gateway import ws as ws_mod
Expand Down Expand Up @@ -87,3 +88,42 @@ def test_ws_disconnect_preserves_and_repoints_reconnectable_session(monkeypatch)
assert server._sessions["plain"]["transport"] is server._detached_ws_transport
finally:
server._sessions.clear()


def test_ws_disconnect_detached_running_session_events_do_not_write_to_stdout(
monkeypatch,
):
"""Detached WS-owned sessions must not leak stream events to stdout.

In the dashboard service, stdout is journald rather than a TUI JSON-RPC
reader. A reconnectable session should park on the detached WS drop
transport while running, then become orphan-reap eligible once idle.
"""

stdout = io.StringIO()
monkeypatch.setattr(server, "_real_stdout", stdout)
server._sessions.clear()
try:
_run_disconnect(
monkeypatch,
lambda t: server._sessions.update(
plain={
"transport": t,
"close_on_disconnect": False,
"session_key": "k",
"running": True,
}
),
)

assert server._sessions["plain"]["transport"] is server._detached_ws_transport
assert not server._ws_session_is_orphaned(server._sessions["plain"])

server._emit("reasoning.delta", "plain", {"text": "must-not-hit-journal"})

assert stdout.getvalue() == ""

server._sessions["plain"]["running"] = False
assert server._ws_session_is_orphaned(server._sessions["plain"])
finally:
server._sessions.clear()
2 changes: 1 addition & 1 deletion tui_gateway/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4615,7 +4615,7 @@ def _(rid, params: dict) -> dict:
return err
# Re-bind to the current client transport for this request. This keeps
# streaming events on the active websocket even if an earlier disconnect
# or fallback moved the session transport to stdio.
# parked the session on the detached-WS drop transport.
if (t := current_transport()) is not None:
session["transport"] = t
with session["history_lock"]:
Expand Down