Issue #33 (job budget, wall_clock_timeout vs deadline_timeout) and Issue #30 (notify-only stall watcher). Reviews of PR #41 in the merge workspace: round 1 (@1673818) claude N1 / codex R1 R2 NOT PASS, agy grok PASS; round 2 (@af0a2a2) grok N1 / codex D1 D2 NOT PASS, claude agy PASS; round 3 (@9200562) 4/4 PASS. Reports and the round-3 identity sidecars go to .agents/reports/<session>/. The design consensus on items A-D is in the VERSIONS.md entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
10 KiB
10 KiB
Review Report: PR #41 Delta Review Round 2 (fix(#30, #33) @ af0a2a2)
- Job ID:
53452ca8 - Reviewer: Antigravity CLI (
agy, session:herdr:reviewer-agy-01, Role:Reviewer) - PR Target: PR #41 Delta (
fix/issue-30-33@af0a2a2, previous review target1673818, basemain8ea65bb/ v4.7.4) - Review Identity & Tree:
loop_id:loop-20260926T052018Z-597343base:1673818bb5fed14ed6fee87884e70d39e81fe91fhead:af0a2a2557768fa316203b177e1b90e9f7283a1atree:0310682605711b1dc7e31cda84ed6697d8ba6ce8
- Verification Worktree:
/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20260926T052018Z-597343/1 - Verdict: PASS
1. Executive Summary & Verification Context
This Round 2 Delta Review assesses commit af0a2a2 on PR #41, which directly addresses the findings raised by reviewers in Round 1:
- N1 (claude, blocking): The bash wrapper previously executed an unbounded
wait "$STALL_PID"afterkill "$STALL_PID". If SIGTERM arrived during the watcher's fork-exec window, bash in the child deferred terminating signals untilexec, causing the wrapper to hang indefinitely until manually killed. - R1 (codex, blocking): If the wrapper itself was killed via SIGTERM, the spawned stall watcher remained orphaned in the background until the job closed.
- R2 (codex, blocking): If a job transitioned to
completedor was re-dispatched during a blocking herdr wait (watcher.wait_once), the watcher previously continued and injected an unprompted nudge into a closed or newly reassigned session. - M8 (agy/codex, test scope):
test_issue30_stall_watch.pypreviously had a coarse module-level skip on non-Linux systems, preventing 48 tests from running on macOS. - M3 / M5 (codex/claude, documentation): Clarified SKILL.md exit code table rows (idle vs wall-clock timeout) and corrected version wording from
before v4.7.5tov4.8.0.
Identity & Cleanliness
git rev-parse HEAD=af0a2a2557768fa316203b177e1b90e9f7283a1agit rev-parse 'HEAD^{tree}'=0310682605711b1dc7e31cda84ed6697d8ba6ce8git diff 1673818...HEAD --check= Clean (0 formatting / whitespace errors)git diff 8ea65bb...HEAD --check= Clean (0 formatting / whitespace errors)- Worktree and control plane statuses: Clean
2. Detailed Technical Audit of Round 1 Corrective Changes
(1) N1 Fix: Bounded Termination in stop_stall_watch
- File:
multi-agent-mux-delegate-job(stop_stall_watch())if kill -0 "$STALL_PID" 2>/dev/null; then kill "$STALL_PID" 2>/dev/null || true for ((i = 0; i < 25; i++)); do kill -0 "$STALL_PID" 2>/dev/null || break sleep 0.2 done if kill -0 "$STALL_PID" 2>/dev/null; then echo "stall watch for $JOB_ID ignored TERM; killed (pid $STALL_PID)" >&2 kill -9 "$STALL_PID" 2>/dev/null || true fi wait "$STALL_PID" 2>/dev/null || true else wait "$STALL_PID" 2>/dev/null || rc=$? fi - Audit Findings:
- Termination Bounded: If SIGTERM is ignored or deferred by the child, polling loops up to 25 iterations × 0.2s (5.0s maximum). Upon expiration,
kill -9(SIGKILL) is dispatched. Because SIGKILL cannot be deferred, caught, or ignored by user processes, the process is guaranteed to die immediately. - No Unbounded Wait:
wait "$STALL_PID"executes only after confirming process exit or dispatching SIGKILL, eliminating any possibility of indefinite hangs. - Reaping Dead Processes: If the watcher exited beforehand,
kill -0returns false, executing theelsebranch to harvest the zombie and record its exit code.
- Termination Bounded: If SIGTERM is ignored or deferred by the child, polling loops up to 25 iterations × 0.2s (5.0s maximum). Upon expiration,
(2) R1 Fix: Lifecycle Binding via --watch-pid
- Files:
multi-agent-mux-delegate-job(start_stall_watch),lib_py/stall_watch.py - Audit Findings:
start_stall_watchpasses--watch-pid "$$"(the PID of the wrapper script).- In
stall_watch.py:run()polls_alive(self.args.watch_pid)at the start of every cycle. If the wrapper process vanishes, the watcher immediately logs an informational message and terminates cleanly with exit codeRC_TERMINAL(0) without mutating the job record. - Permission & Stale Check Safety (
_alive):def _alive(pid: int) -> bool: try: os.kill(pid, 0) except ProcessLookupError: return False except PermissionError: return True return TrueProcessLookupError(ESRCH) accurately reports that the process no longer exists.PermissionError(EPERM) is safely handled by returningTrue, preventing false-positive terminations if PID permissions differ. - No Fragile Trap Synthesis: Bounding lifecycle via process polling avoids fragile
trap ... EXITsynthesis in bash, which is prone to signal dropouts and subshell inheritance discrepancies across bash versions.
(3) R2 Fix: Dispatch Verification via _dispatch_open()
- File:
lib_py/stall_watch.py - Audit Findings:
_dispatch_open()readsjob.jsonand returnsFalseif:- Status is in
TERMINAL_STATUSES(completed,error,cancelled). expect_dispatch_tokenis present and disagrees withrec["dispatch_token"].attempt_idis present and disagrees withrec["attempt_id"].
- Status is in
- 3-Point Verification: Checked (a) at the head of
run(), (b) immediately after returning from blocking herdr wait (watcher.wait_once), and (c) immediately before sending the nudge prompt. - Robust Exception Handling:
FileNotFoundErrorimmediately returnsFalse(job removed).OSError/ValueErrorreturnsTrue(transient read collision during concurrent write), avoiding premature termination while re-verifying on the subsequent iteration. - Refusal Handling in
_notify:_notify()now returns a boolean status. If publishingstalled:idlefails or is rejected by registry CAS,st.nudged = Trueis marked to prevent retry, andself.src.prompt(...)is withheld. - Blocked Path Adequacy: The
blockedbranch does not require prompt withholding because it never injects prompts or keystrokes into the agent's pane (pure notify-only).
(4) M8 Fix: Scope Narrowing of macOS Test Skips
- File:
tests/test_issue30_stall_watch.py - Audit Findings:
- Removed module-level
pytestmark = pytest.mark.skipif(...). - Scoped Linux skip (
@PROC) strictly totest_s6_the_heartbeat_timer_is_not_a_background_childwhich requires/proc. - Switched stub timestamp comparison in
test_r4_a_missed_transition_restarts_the_idle_windowfromtime.monotonic()totime.time(). Because macOS assigns each subprocess an independent monotonic reference clock, cross-process timestamp serialization viaobserved.jsonrequired epoch timestamps. - On macOS, 47 tests pass and exactly 1 test (
test_s6) is skipped as expected.
- Removed module-level
3. Test Suite Verification & Test Determinism
All tests were executed against the verification worktree (loop-20260926T052018Z-597343/1):
-
tests/test_issue30_stall_watch_stop.py(New Suite, 7/7 PASSED in 36.24s):test_stop_stall_watch_is_bounded_when_the_watcher_ignores_term: Stand-in ignoring SIGTERM is terminated by SIGKILL within 5s; total elapsed time4s < took < 12s.test_the_wrapper_returns_when_its_watcher_ignores_term: End-to-end wrapper execution with SIGTERM-ignoring watcher returns cleanly via broker-unavailable path within 40s.test_the_watcher_stops_once_its_wrapper_is_gone: Watcher exits rc 0 upon detecting killed wrapper PID.test_a_terminated_wrapper_leaves_no_watcher_behind(R1): Real wrapper functions verify orphaned watcher exits rc 0 within 30s.test_a_job_completed_during_the_wait_is_not_nudged(R2): Nudge withheld when job completes during wait.test_a_job_redispatched_during_the_wait_is_not_nudged(R2): Nudge withheld when job token/attempt changes.test_a_refused_idle_notice_withholds_the_nudge(R2): Nudge withheld whenstalled:idlepublication is refused.- Determinism: No leftover processes, no flaky sleep races, deterministic process cleanup across all tests.
-
tests/test_issue30_stall_watch.py:- 47 PASSED, 1 SKIPPED (macOS /proc restriction).
- Occasional CONNACK timeout on live public HiveMQ broker (
test_r2[True]) is a documented environmental limitation (M9).
-
Regression Suites:
tests/test_issue14_delivery_ack.py: PASSEDtests/test_tier1_unit.py: PASSED
4. Round 1 Review Feedback Status Table
| Issue / Item | Description | Status in Round 2 | Resolution Details |
|---|---|---|---|
| N1 (claude) | Unbounded wait in stop_stall_watch when SIGTERM is swallowed |
Closed | Added 5s polling loop followed by SIGKILL; verified via 2 tests. |
| R1 (codex) | Orphaned watcher when wrapper killed by SIGTERM | Closed | Added --watch-pid $$ and watcher self-termination; verified via 2 tests. |
| R2 (codex) | Spurious nudge on completed or re-dispatched job | Closed | Added _dispatch_open() 3-point check & _notify boolean gate; verified via 3 tests. |
| M5 (claude) | Version wording in SKILL.md (before v4.7.5 -> v4.8.0) |
Closed | Corrected to before v4.8.0. |
| M8 (agy/codex) | Overly broad module-level macOS test skip | Closed | Narrowed skip strictly to test_s6; 47 tests now run and pass on macOS. |
| M3 (codex) | SKILL.md exit code table row precision | Closed | Clarified idle timeout (row 2) and wall-clock timeout (row 3). |
| M7 | MAM_TEST_UNRELIABLE_AGENT test seam |
Follow-up | Unreliable adapter seam deferred to future testing issue (non-blocking). |
| M9 | Public HiveMQ broker CONNACK delay | Documented | Documented public broker network latency limitation (non-blocking). |
| M1/M2/M4/M6 | Transfer of remaining bash wrapper logic to Python | #17 Item 4 | Deferred to shell reduction effort #17 Item 4 by consensus (non-blocking). |
5. Conclusion
All blocking reviewer concerns from Round 1 (N1, R1, R2) have been thoroughly resolved with elegant, minimal, and bounded code. The new regression suite test_issue30_stall_watch_stop.py provides 100% test coverage of all edge cases with zero race conditions or process leaks.
[VERDICT: PASS]