From 1e92ddc563cf5859e5da3f74b6ff680d1eda54ce Mon Sep 17 00:00:00 2001 From: Godopu Date: Fri, 2 Oct 2026 08:23:31 +0900 Subject: [PATCH] test(followups): F6 - the wrapper vanishes inside the second wait; the <= 60 s is the herdr wait's delegate-job SKILL.md said a watcher outlives a killed wrapper by "one wait, <= 60 s". That bound is the herdr wait's; a wrapper that goes while a notice is being published is noticed when that publish ends. The text now says both (PR #41 codex round 3). "a wrapper gone during the wait" switched alive() by call count, so the watcher saw the wrapper gone at the next loop head and never entered the second wait (ticks == [1] with that band). The wrapper now vanishes inside the second herdr wait (_during_notice's in_wait hook), and the test checks that the second wait happened and that alive() was true until then. The same test fails when the post-wait _wrapper_gone() check is removed. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe --- .../multi-agent-mux-delegate-job/SKILL.md | 5 ++-- tests/test_issue30_stall_watch_stop.py | 23 ++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/.agents/skills/multi-agent-mux-delegate-job/SKILL.md b/.agents/skills/multi-agent-mux-delegate-job/SKILL.md index 230cf36..72e4a19 100644 --- a/.agents/skills/multi-agent-mux-delegate-job/SKILL.md +++ b/.agents/skills/multi-agent-mux-delegate-job/SKILL.md @@ -50,8 +50,9 @@ multi-agent-mux-delegate-job submit \ # MAM_STALL_IDLE_S (600) / MAM_STALL_HERDR_FAIL_S (120) / MAM_STALL_WATCH=0: the stall watcher (Issue #30). # The watcher lives as long as the job's wrapper: the wrapper stops it (KILL, at once) # when its subscriber ends, and the watcher exits on its own once the wrapper pid is gone - # (checked after every herdr wait, so it can outlive a killed wrapper by one wait, <= 60 s, - # and never sends a notice or a nudge after it saw the wrapper gone). + # (checked after every herdr wait, so it can outlive a killed wrapper by one herdr wait, + # <= 60 s - or, when the wrapper goes while a notice is being published, until that + # publish ends - and never sends a notice or a nudge after it saw the wrapper gone). # --parent-job (Issue #34): this job is delegated from job , which # must exist in the registry (else exit 1). A loop waiting on waits for # it too and shows it to reviewers. Default: $MAM_JOB_ID (the remote runner diff --git a/tests/test_issue30_stall_watch_stop.py b/tests/test_issue30_stall_watch_stop.py index b2b9d5c..581c2c7 100644 --- a/tests/test_issue30_stall_watch_stop.py +++ b/tests/test_issue30_stall_watch_stop.py @@ -329,9 +329,10 @@ def test_a_refused_idle_notice_withholds_the_nudge(tmp_path, monkeypatch): # round-2 review (codex D1/D2) - the last gate sits between the notice and the keys # --------------------------------------------------------------------------- -def _during_notice(tmp_path, monkeypatch, change, alive=lambda pid: True): +def _during_notice(tmp_path, monkeypatch, change, alive=lambda pid: True, in_wait=lambda tick: None): """Idle past S on the second tick; `change(rec)` runs inside the notice's - publish, as another actor would act while it waits on the wire.""" + publish, as another actor would act while it waits on the wire, and + `in_wait(tick)` inside each herdr wait.""" from lib_py import stall_watch import argparse import uuid @@ -343,6 +344,7 @@ def _during_notice(tmp_path, monkeypatch, change, alive=lambda pid: True): def fake_wait_once(target, until=(), timeout_ms=None, session=None, run=None): from lib_py.loop import watcher as w ticks.append(1) + in_wait(len(ticks)) if len(ticks) == 1: return w.WaitOutcome(w.MATCHED, agent={"agent_status": "idle", "state_change_seq": 1, "pane_id": "w1:p1"}) if len(ticks) > 2: # a watcher that did not stop: end the run through the record @@ -416,10 +418,19 @@ def test_an_unreadable_record_at_the_last_gate_withholds_the_nudge(tmp_path, mon def test_a_wrapper_gone_during_the_wait_gets_no_notice_and_no_nudge(tmp_path, monkeypatch): """Idle past S and the wrapper vanished during the same wait: the watcher leaves before judging - no notice, no keys, the record untouched.""" - calls = [] + gone, seen = [], [] def alive(pid): - calls.append(pid) - return len(calls) <= 2 # alive at the first loop head and after the first wait, gone after - rc, prompts, notes = _during_notice(tmp_path, monkeypatch, lambda rec: None, alive=alive) + seen.append((len(ticks), not gone)) + return not gone + + ticks = [] + + def in_wait(tick): + ticks.append(tick) + if tick == 2: + gone.append(True) # inside the second wait, while the watcher is blocked in herdr + rc, prompts, notes = _during_notice(tmp_path, monkeypatch, lambda rec: None, alive=alive, in_wait=in_wait) assert rc == 0 and notes == [] and prompts == [], (prompts, notes) + assert ticks == [1, 2], "the wrapper must vanish inside the second wait, not before it" + assert seen[-1] == (2, False) and all(ok for _, ok in seen[:-1]), seen