diff --git a/tests/tools/test_completed_process_results.py b/tests/tools/test_completed_process_results.py index 4048d8c069..01b6b99dc2 100644 --- a/tests/tools/test_completed_process_results.py +++ b/tests/tools/test_completed_process_results.py @@ -36,8 +36,8 @@ def test_headless_terminal_result_survives_cli_exit(tmp_path): print("review stderr", file=sys.stderr) sys.exit(7) '''), encoding="utf-8") - argv = [sys.executable, str(child), str(release)] - command = subprocess.list2cmdline(argv) if os.name == "nt" else shlex.join(argv) + # The local terminal backend uses bash, including Git Bash on Windows. + command = shlex.join(path.as_posix() for path in (Path(sys.executable), child, release)) observed = [] class Provider(http.server.BaseHTTPRequestHandler): @@ -135,7 +135,7 @@ def test_headless_terminal_result_survives_cli_exit(tmp_path): recovered = read_result(home) assert recovered["result"]["status"] == "exited", recovered - assert recovered["status"]["exit_code"] == 7 + assert recovered["status"]["exit_code"] == 7, recovered assert "SYNTHETIC_REVIEW_COMPLETE" in recovered["result"]["output"] assert "review stderr" in recovered["result"]["output"] assert recovered["replayed"] is False @@ -171,8 +171,10 @@ def test_receipts_are_bounded_redacted_and_session_scoped(tmp_path, monkeypatch) recovered = fresh.get(sessions[-1].id) assert recovered.owner_task_id == sessions[-1].owner_task_id assert len(recovered.output_buffer) <= MAX_OUTPUT_CHARS - assert [s["session_id"] for s in fresh.list_sessions(task_id="owner-2")] == [recovered.id] - assert fresh.list_sessions(task_id="unrelated", session_key="unrelated") == [] + assert fresh.list_sessions() == [] # Status/liveness scans stay in memory. + assert [s["session_id"] for s in fresh.list_sessions( + task_id="owner-2", include_retained=True)] == [recovered.id] + assert fresh.list_sessions(task_id="unrelated", session_key="unrelated", include_retained=True) == [] assert fresh.get("proc_0000") is None # Ambiguous across durable results. assert fresh.completion_queue.empty() for path in paths: diff --git a/tools/process_registry.py b/tools/process_registry.py index af8de52f29..222326d78d 100644 --- a/tools/process_registry.py +++ b/tools/process_registry.py @@ -1781,7 +1781,7 @@ class ProcessRegistry(ProcessCheckpointMixin): """O(1) running count for status-bar polling; dict ``len()`` is atomic, no lock.""" return len(self._running) - def list_sessions(self, task_id: str = None, session_key: str = None) -> list: + def list_sessions(self, task_id: str = None, session_key: str = None, *, include_retained: bool = False) -> list: """Running and recently-finished processes for ``task_id`` and/or ``session_key``; cross-task entries sharing the gateway session (a forgotten preview server blocking session reset) are flagged ``"session_scoped": true``. @@ -1791,7 +1791,9 @@ class ProcessRegistry(ProcessCheckpointMixin): surfaced too, even if they belong to a different task — so the agent can discover a forgotten preview server that is blocking session reset (#29177). """ - sessions = load_completed_results() + # Only an explicit tool query reads historical receipts. Status bars and + # gateway liveness scans call this frequently and need the live registry. + sessions = load_completed_results() if include_retained else {} with self._lock: sessions.update(self._finished) sessions.update(self._running) @@ -1995,7 +1997,8 @@ def _list_processes(task_id) -> dict: session_key = get_current_session_key(default="") or "" return {"processes": [ _redact_process_result(p) - for p in process_registry.list_sessions(task_id=task_id, session_key=session_key or None)]} + for p in process_registry.list_sessions( + task_id=task_id, session_key=session_key or None, include_retained=True)]} # action -> (handler(session_id, args) -> dict, redact output?). Output-bearing