From 16123f9974a0b0a09b07c857c282562f31856c36 Mon Sep 17 00:00:00 2001 From: Radical Edward Date: Mon, 25 May 2026 02:49:11 +0200 Subject: [PATCH 1/2] =?UTF-8?q?fix(process=5Fregistry):=20keep=20poll()=20?= =?UTF-8?q?read-only=20=E2=80=94=20do=20not=20mark=20completions=20consume?= =?UTF-8?q?d?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/process_registry.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/process_registry.py b/tools/process_registry.py index 38c35b3c5a0b..b566dceb4568 100644 --- a/tools/process_registry.py +++ b/tools/process_registry.py @@ -997,7 +997,6 @@ def poll(self, session_id: str) -> dict: } if session.exited: result["exit_code"] = session.exit_code - self._completion_consumed.add(session_id) if session.detached: result["detached"] = True result["note"] = "Process recovered after restart -- output history unavailable" From d3d7b17e494d9b68746f44dfa0af9aacafdc8d35 Mon Sep 17 00:00:00 2001 From: Radical Edward Date: Mon, 25 May 2026 02:49:37 +0200 Subject: [PATCH 2/2] test(process_registry): verify poll() does NOT mark completions consumed Regression guard for #10156. Ensures that poll() (a read-only status query) does not have the side effect of consuming completion notifications, which would silently suppress notify_on_complete watcher delivery. --- tests/tools/test_process_registry.py | 43 ++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/tests/tools/test_process_registry.py b/tests/tools/test_process_registry.py index 10e4421e5f02..60ceda47bc52 100644 --- a/tests/tools/test_process_registry.py +++ b/tests/tools/test_process_registry.py @@ -102,6 +102,49 @@ def test_poll_exited(self, registry): assert result["status"] == "exited" assert result["exit_code"] == 0 + def test_poll_does_not_mark_completion_consumed(self, registry): + """poll() must NOT mark completions consumed (regression guard for #10156). + + notify_on_complete watcher notifications were silently suppressed + because poll() added sessions to _completion_consumed. This caused + drain_notifications() to skip completion events after a poll() call. + + poll() is a read-only status query — it should not have the side + effect of consuming the notification event. + """ + sid = "proc_poll_not_consumed" + s = _make_session(sid=sid, exited=True, exit_code=0, output="done") + registry._finished[s.id] = s + + # Queue a completion event for this session + registry.completion_queue.put({ + "type": "completion", + "session_id": sid, + "command": "echo done", + "exit_code": 0, + "output": "done", + }) + + try: + # Poll should return the exited status + result = registry.poll(sid) + assert result["status"] == "exited" + assert result["exit_code"] == 0 + + # After poll(), the session should NOT be marked as consumed + assert not registry.is_completion_consumed(sid), ( + "poll() should not mark completions consumed" + ) + + # drain_notifications() should still return the event + notifications = registry.drain_notifications() + assert len(notifications) == 1 + assert notifications[0][0]["session_id"] == sid + finally: + registry._completion_consumed.discard(sid) + while not registry.completion_queue.empty(): + registry.completion_queue.get_nowait() + # ========================================================================= # Orphaned-pipe reconciliation (issue #17327)