Files
multi-agent-mux/.agents/reports/reviewer-agy-01/report-53452ca8.md
T
GodopuandClaude Opus 5.5 53f87e4560 release(v4.8.0): bump framework and eight skills to 4.8.0 in lockstep; archive PR #41 reviews (three rounds)
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
2026-09-26 16:08:37 +09:00

10 KiB
Raw Blame History

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 target 1673818, base main 8ea65bb / v4.7.4)
  • Review Identity & Tree:
    • loop_id: loop-20260926T052018Z-597343
    • base: 1673818bb5fed14ed6fee87884e70d39e81fe91f
    • head: af0a2a2557768fa316203b177e1b90e9f7283a1a
    • tree: 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:

  1. N1 (claude, blocking): The bash wrapper previously executed an unbounded wait "$STALL_PID" after kill "$STALL_PID". If SIGTERM arrived during the watcher's fork-exec window, bash in the child deferred terminating signals until exec, causing the wrapper to hang indefinitely until manually killed.
  2. R1 (codex, blocking): If the wrapper itself was killed via SIGTERM, the spawned stall watcher remained orphaned in the background until the job closed.
  3. R2 (codex, blocking): If a job transitioned to completed or 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.
  4. M8 (agy/codex, test scope): test_issue30_stall_watch.py previously had a coarse module-level skip on non-Linux systems, preventing 48 tests from running on macOS.
  5. 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.5 to v4.8.0.

Identity & Cleanliness

  • git rev-parse HEAD = af0a2a2557768fa316203b177e1b90e9f7283a1a
  • git rev-parse 'HEAD^{tree}' = 0310682605711b1dc7e31cda84ed6697d8ba6ce8
  • git 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 -0 returns false, executing the else branch to harvest the zombie and record its exit code.

(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_watch passes --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 code RC_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 True
      
      ProcessLookupError (ESRCH) accurately reports that the process no longer exists. PermissionError (EPERM) is safely handled by returning True, preventing false-positive terminations if PID permissions differ.
    • No Fragile Trap Synthesis: Bounding lifecycle via process polling avoids fragile trap ... EXIT synthesis 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() reads job.json and returns False if:
      1. Status is in TERMINAL_STATUSES (completed, error, cancelled).
      2. expect_dispatch_token is present and disagrees with rec["dispatch_token"].
      3. attempt_id is present and disagrees with rec["attempt_id"].
    • 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: FileNotFoundError immediately returns False (job removed). OSError / ValueError returns True (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 publishing stalled:idle fails or is rejected by registry CAS, st.nudged = True is marked to prevent retry, and self.src.prompt(...) is withheld.
    • Blocked Path Adequacy: The blocked branch 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 to test_s6_the_heartbeat_timer_is_not_a_background_child which requires /proc.
    • Switched stub timestamp comparison in test_r4_a_missed_transition_restarts_the_idle_window from time.monotonic() to time.time(). Because macOS assigns each subprocess an independent monotonic reference clock, cross-process timestamp serialization via observed.json required epoch timestamps.
    • On macOS, 47 tests pass and exactly 1 test (test_s6) is skipped as expected.

3. Test Suite Verification & Test Determinism

All tests were executed against the verification worktree (loop-20260926T052018Z-597343/1):

  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 time 4s < 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 when stalled:idle publication is refused.
    • Determinism: No leftover processes, no flaky sleep races, deterministic process cleanup across all tests.
  2. 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).
  3. Regression Suites:

    • tests/test_issue14_delivery_ack.py: PASSED
    • tests/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]