fix(tui): log 4001 session-not-found rejections for diagnosability

Messages sent into a session whose in-memory runtime was detached on WS
disconnect and orphan-reaped vanished silently: _sess_nowait returned
4001 with no log line, so 'request arrived and was rejected' was
indistinguishable from 'request never arrived' in a 'message vanished'
report. Log a WARNING with the session id and request id on every
session-scoped RPC rejected against an unknown runtime id.

Adds a regression test asserting the 4001 response and the warning.

Closes #90428
This commit is contained in:
A2chitect
2026-08-19 20:25:15 -07:00
committed by Teknium
parent 525597c9c3
commit c305839442
2 changed files with 52 additions and 2 deletions
+33
View File
@@ -1,4 +1,5 @@
import json
import logging
import os
import subprocess
import sys
@@ -341,6 +342,38 @@ def test_compute_host_explicit_images_do_not_clear_later_attachment(monkeypatch)
assert session["attached_images"] == ["/tmp/c.png"]
def test_prompt_submit_unknown_session_logs_warning(caplog):
"""A submit against a reaped runtime id must leave a diagnosable trace.
Regression for #90428: messages sent into a session whose in-memory
runtime was detached on WS disconnect and orphan-reaped vanished
silently — the 4001 was never logged, so "request arrived and was
rejected" was indistinguishable from "request never arrived".
"""
for session in list(server._sessions.values()):
server._teardown_session(session)
server._sessions.clear()
with caplog.at_level(logging.WARNING, logger="tui_gateway.server"):
resp = _dispatch_sync(
{
"id": "r1",
"method": "prompt.submit",
"params": {"session_id": "gone-sid", "text": "hello"},
}
)
assert resp == {
"jsonrpc": "2.0",
"id": "r1",
"error": {"code": 4001, "message": "session not found"},
}
assert any(
"session-scoped RPC rejected" in rec.message and "gone-sid" in rec.message
for rec in caplog.records
)
def test_prompt_submit_fails_open_inline_when_compute_host_dispatch_breaks(monkeypatch):
class _BrokenSupervisor:
def submit_turn(self, frame, *, on_complete=None):
+19 -2
View File
@@ -2741,8 +2741,25 @@ def _start_agent_build(sid: str, session: dict) -> None:
def _sess_nowait(params, rid):
s = _sessions.get(params.get("session_id") or "")
return (s, None) if s else (None, _err(rid, 4001, "session not found"))
sid = params.get("session_id") or ""
s = _sessions.get(sid)
if s:
return (s, None)
# A session-scoped RPC hit a runtime id the gateway no longer holds
# (detached on WS disconnect and orphan-reaped, LRU-evicted, or torn down
# after an idle TTL). The client is expected to recover via
# session.resume on the STORED session id, but a plain stale-id send
# leaves no trace anywhere when the resume never fires — every RPC in
# this class returned a silent 4001. Log it so a "message vanished"
# report is diagnosable as "request arrived and was rejected" instead of
# "request never arrived" (see #90428).
logger.warning(
"session-scoped RPC rejected: session_id=%r not in memory "
"(detached/reaped runtime; client should resume the stored session), rid=%r",
sid,
rid,
)
return (None, _err(rid, 4001, "session not found"))
def _sess(params, rid):