Files
multi-agent-mux/.agents/reports/reviewer-grok-01/report-7eab3b75.md
T
GodopuandClaude Opus 5.5 ca688dcef4 release(v4.8.9): tests no longer take the checkout's loop lock - archive the review reports and bump the lockstep version
Merge-workspace review loop (--all-reviewer): round 1 (@a93c9bf) 4/4
PASS. Full suite at a93c9bf on macOS / Python 3.9 / herdr 0.9.3 with
WORKSPACE_ROOT set to an empty sentinel: 1752 passed / 0 failed / 38
skipped; the sentinel stayed empty, the review worktree had no .mam, and
the main checkout's shim was not touched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
2026-10-09 10:59:38 +09:00

7.7 KiB

Review 7eab3b75 — tests must not take the checkout loop lock

Head a93c9bf29279f65ce0b9c9fb2c1aeea90a659420 (tree 741c19ba4c17601768b06684d5843d218a72beb1) gives the two checkout run_loop.sh runs that reach the lock a marker under the test's tmp_path. The new isolation test passes, and the same command without that marker leaves an empty .mam/ behind.

Snapshot /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261008T234013Z-4f5d21/1 only. Base 97c2946ee0e11eabcecafe6407755c33c0289218 (control-plane main, still that commit, porcelain empty). One commit, a93c9bf test(loop-lock): the b13 runs of the checkout's run_loop.sh take a loop lock of their own. Three files, +55, all under tests/. git diff --check is clean. .agents/skills and VERSIONS.md are outside the diff. No version bump.

Interpreter: worktree .venv Python 3.9.6, bash 3.2.57. The suite set AGENT_PYTHON_BIN to that interpreter and put its bin first on PATH.

What the commit does

run_loop.sh:15 sets MAM_REAL_ROOT to the checkout that contains the script. run_loop.sh:181 takes ${MAM_LOOP_MARKER:-$MAM_REAL_ROOT/.mam/loop-guard-active} after the arguments parse. loop_lock.sh:66 mkdir -ps the marker's parent. The trap at run_loop.sh:264 removes the marker file on the way out and leaves the directory.

tests/test_o3_scoped_guard.py:391 and :470 set MAM_LOOP_MARKER to tmp_path / "loop-guard-active" for test_b13_reexec_preserves_original_argv and test_b13_no_freeze_switch_disables_reexec. Both still run the checkout script with cwd at the repo root and --creator nonexistent-agent. The marker's parent is the test directory, so the mkdir -p does not create the checkout's .mam.

The lock-behavior tests keep their own paths. test_o2_12_run_loop_exits_on_lock_failure (tests/test_o2_race_free_lock.py:205) still points MAM_LOOP_MARKER at the marker the test is holding. The o3 scripts that call mam_acquire_loop_lock directly (test_z9_* around :177, :228, :266, :298, and test_b13_release_guard_cleans_up_and_releases_lock at :438) still use tmp_path as REPO_ROOT. Nothing in conftest.py sets the marker, and it is not autouse.

tests/test_loop_marker_isolation.py adds L1. It copies this checkout, runs the seven nodes below with WORKSPACE_ROOT naming the copy, and requires the copy to have no .mam at all. tests/test_issue17_workspace_root_isolation.py:71 adds the same directory check to W1. W1 already required .mam to contain no files; an empty directory left by mkdir -p passed that check.

Survey

A checkout run_loop.sh reaches mam_acquire_loop_lock (run_loop.sh:248) only after argument parsing. --help and usage exit at :66. A missing --creator/--task, --target-agent, --planner without --plan, a bad --max-rebut, and the legacy-shell refusal of the Python-only options (:136-141) all exit before the lock. bash -n and a read_text do not execute it. A script copied under tmp_path or mam_sandbox has its own MAM_REAL_ROOT.

The two b13 runs were the only executions of this checkout's run_loop.sh that got as far as the lock without MAM_LOOP_MARKER or a temp MAM_REAL_ROOT. The other runs that do reach the lock already name a marker.

Location Why this is not the checkout lock
test_o3_scoped_guard.py:391, :470 this commit sets MAM_LOOP_MARKER under tmp_path
test_issue5_phase3_loop.py:163, :181 MAM_LOOP_MARKER is tmp_path / "loop-marker"
test_loop_cli.py:53 MAM_LOOP_MARKER is tmp_path / "loop-guard"
test_loop_cli.py:100 _loop_env sets the marker and MAM_REAL_ROOT to mam_sandbox
test_o2_race_free_lock.py:205 the marker is the one the test holds, on purpose
test_issue25_plan_reuse.py:370 marker is set; MAM_LOOP_PY=0 plus --plan-file exits at run_loop.sh:140 before acquire
test_issue21_worktree_prereqs.py:404 marker is set on mam_sandbox; the same refusal exits before acquire
test_issue21_loop_b.py:238 marker and WORKSPACE_ROOT are tmp_path; the same refusal exits before acquire
test_issue21_acceptance.py:298 the script is the temp repo's copy, and the marker is set
test_issue36_prompt_transport.py:539 copied skills tree; marker is ctl/.mam/loop-marker
test_tier4_e2e.py:433 the script lives under mam_sandbox, so MAM_REAL_ROOT is that sandbox
test_o1_rebuttal.py:72, :76 --help, then --max-rebut abc; both exit in the parser
test_loop_cli.py:36, :45, :63, :71 --help, missing args, --target-agent, --planner without --plan
test_issue5_phase3_loop.py:151, test_issue5_phase4_runtime.py:141, test_issue25_plan_reuse.py:355, test_o3_scoped_guard.py:422 --help
test_o2_race_free_lock.py:232, test_b7_diff_untracked.py:191 bash -n
test_b7_diff_untracked.py:174 sources diff_collect.sh, which sources lib_int.sh
test_deploy_freshness.py:473, test_issue5_phase0_contracts.py:481, test_o3_scoped_guard.py:137 the file is read as text
test_o3_scoped_guard.py:177 and the sibling z9 scripts loop_lock.sh directly, REPO_ROOT is tmp_path

lib_py/loop/runner.py:1226 releases MAM_LOOP_MARKER or self.mam_dir / "loop-guard-active". The acquire lives in run_loop.sh. L1's node list also names test_p7b_the_legacy_shell_loop_refuses_the_option, which exits before the lock. That node still belongs in the regression: it runs this checkout's script.

Requirement 3 — the unfixed command leaves .mam/

Product run_loop.sh is unchanged, so the command test_b13_no_freeze_switch_disables_reexec used to run is the pre-fix case. The script tree was a copy of the snapshot's .agents under /tmp/7eabpre, not the snapshot and not the control-plane checkout. WORKSPACE_ROOT pointed at a side directory, so lib.sh's shim was not the thing creating the copy's .mam. Inherited HERDR_*, MAM_*, MQTT_*, and SKS_* were removed.

Without MAM_LOOP_MARKER, bash run_loop.sh --creator nonexistent-agent --task "test goal" exited 1 and left /tmp/7eabpre/old-checkout/.mam/ as an empty directory. The marker file was gone. The shim landed in the side directory (.mam/shim/herdr).

The same command with MAM_LOOP_MARKER set to /tmp/7eabpre/own-marker/loop-guard-active exited 1 and left the second copy with no .mam. The marker file was released. The snapshot's .mam stayed absent, and the control-plane lock stayed mtime=1791502813 size=70.

Requirement 4 — suite

AGENT_PYTHON_BIN and PATH pointed at the worktree .venv. Inherited HERDR_*, MAM_*, MQTT_*, SKS_*, and WORKSPACE_ROOT were cleared in the parent. PYTHONDONTWRITEBYTECODE=1, -p no:cacheprovider. Basetemp directories are /tmp/7eabA.n4k8Qw and /tmp/7eabB.p2m7Lx. Neither path contains mam-review.

The four files are tests/test_issue17_workspace_root_isolation.py, tests/test_loop_marker_isolation.py, tests/test_o2_race_free_lock.py, and tests/test_o3_scoped_guard.py.

(a) WORKSPACE_ROOT unset: 57 passed in 47.60s, exit 0.

(b) WORKSPACE_ROOT=/tmp/7eabsent.t8c3Vr (the directory existed): 57 passed in 47.43s, exit 0. The sentinel was empty afterwards.

pytest_configure drops a WORKSPACE_ROOT whose real path is some other directory, so the outer sentinel staying empty is that drop. L1 and W1 keep a root that names the copied checkout, and both require that copy to have no .mam. Both passed inside (a) and (b).

After the pair: snapshot .mam absent, source worktree .mam absent, snapshot and worktree porcelain empty, control-plane main still 97c2946 with empty porcelain, control-plane loop-guard-active still mtime=1791502813 size=70, ~/.config/herdr/config.toml mtime 1784456394, directory mtime 1791382452.

[VERDICT: PASS]