Final HEAD263d6bf: claude, agy and grok PASS (delta review of the lifecycle-lock commits on top of theirab842f1PASS), codex PASS (its own round after F1/F2). Full suite at263d6bf: 906 passed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
5.3 KiB
Delta Review Report: Fix Issue #18 (ab842f1...263d6bf)
Reviewer: Antigravity (herdr:reviewer-agy-01)
Job ID: abd2777b
Target Commit Range: ab842f1...263d6bf on branch fix/issue-18 (Base: main @ 139bdd4, v4.4.5)
Commits in Delta:
2b1d590—fix(issue-18): serialize the heartbeat timer lifecycle per job263d6bf—fix(issue-18): let an exiting heartbeat timer skip the busy lifecycle lock; kill a timer whose pid cannot be recorded
Date: 2026-09-14
Status: Review Complete
1. Scope & Objective
The full fix/issue-18 implementation through commit ab842f1 was previously reviewed and approved ([VERDICT: PASS]). This delta review evaluates the two subsequent follow-up commits (2b1d590 and 263d6bf) addressing codex feedback regarding heartbeat timer lifecycle synchronization, deadlocks, and untracked child processes.
2. Delta Code Analysis & Verification
2.1 Per-Job Lifecycle Serialization (lifecycle_lock, 2b1d590)
- Mechanism: Introduced
lifecycle_lock(job_id, registry_dir, blocking=True)using<registry>/<job>.heartbeat.lockviafcntl.flock. - Atomic Operations:
start_timerandstop_timerexecute under this lock. Observing, draining, killing a previous owner, spawning a replacement, and publishing the PID to.heartbeat.pidnow constitute a single serialized ownership transaction. - Barrier Check Before Arming:
start_timerchecks job status andbarrier_blocks()under the lifecycle lock. If a terminal event was raised,start_timerlogs and returns 0 without spawning a timer. - Lock Hierarchy & Deadlock Freedom:
- Draining in-flight beats during
stop_timerdoes not hold theregistry_lock/ sequence lock. In-flight beats are free to acquireregistry_lock, check the barrier, allocate sequence numbers, and finish. - In
publish_event.py,raise_barrieracquires and releasesregistry_lock, thenstop_timeracquires and releaseslifecycle_lock, and finallyprepare_eventacquiresregistry_lockfor the terminal event. Lock nesting is non-existent.
- Draining in-flight beats during
2.2 Codex F1: Non-Blocking Shutdown Lock Cycle Removal (263d6bf)
- Problem: When a parent (stopper or replacement
start_timer) holdslifecycle_lockwhile draining an exiting timer, if the child timer attempted to acquirelifecycle_lock(blocking=True)during shutdown to remove its own pidfile, a parent/child lock cycle occurred, forcing the parent to wait until the drain deadline and kill the child withSIGKILL. - Resolution:
heartbeat.run()now attemptslifecycle_lock(job_id, registry_dir, blocking=False) as owned.- If
ownedisTrue, it compares and unlinksown_pidfile. - If
ownedisFalse, it means another lifecycle operation (stop/replace) currently owns the lifecycle and is draining this process. The exiting timer cleanly skips pidfile manipulation, allowing the parent to handle pidfile deletion.
- If
- Verification: Verified by
test_an_idle_timer_stops_gracefully_well_before_the_drain_deadline. An idle timer terminates cleanly within 0.15s without triggering theSIGKILLfallback.
2.3 Codex F2: Untracked Child Process & Fault Recovery (263d6bf)
- Problem: If writing the pidfile failed after spawning the background timer process (e.g. disk full / ENOSPC), an untracked heartbeat timer could remain running in the background.
- Resolution: In
_spawn_timer_locked, writing the pidfile is wrapped intry...except BaseException. On failure:proc.kill()andproc.wait(timeout=5)terminate and reap the newly spawned process immediately.- Any partial or corrupted pidfile matching
proc.pidis unlinked. - The exception is re-raised, and
lifecycle_lockis safely released in the context manager'sfinally:block.
- Verification: Verified by
test_a_timer_whose_pid_cannot_be_recorded_is_killed_at_once(injected ENOSPC fault) andtest_a_timer_that_cannot_be_spawned_leaves_nothing_behind.
3. Verification & Test Suite Execution
All 31 tests in tests/test_issue18_liveness_contract.py and regression checks were executed:
.venv/bin/python -m pytest tests/test_issue18_liveness_contract.py -v
Result: 31 passed in 109.41s (0 failures, 0 skips).
.venv/bin/python -m pytest tests/test_version_consistency.py -v
Result: 3 passed in 0.85s.
git diff 139bdd4...HEAD --check
bash -n .agents/skills/lib.sh .agents/skills/multi-agent-mux-delegate-job/multi-agent-mux-delegate-job
Result: Clean rc 0 across formatting and syntax checks.
4. Documentation & Version Consistency
MESSAGING.md§2.5: Accurately describes the per-job lifecycle lock<job>.heartbeat.lock, non-blocking child cleanup, and failed-spawn cleanup.VERSIONS.md: Updated to state 31 tests intests/test_issue18_liveness_contract.py, documenting the 4th/5th review round additions.- Lockstep 4.5.0: Maintained across all skills and framework manifests.
5. Final Verdict
The delta changes in 2b1d590 and 263d6bf are correct, eliminate potential deadlocks and untracked timer leaks, and cleanly pass all regression tests.
[VERDICT: PASS]