Merge-workspace review loop (--all-reviewer): round 1 (@d198dd5) claude agy PASS, grok codex NOT PASS (the same inherited WORKSPACE_ROOT in eleven more modules; test_orc_onboard's import-time env); fixed in8b47cc7. Round 2 claude grok NOT PASS (test_o2_race_free_lock, test_o1_rebuttal missed), agy codex PASS; fixed inac48cb7. Round 3 (@ac48cb7) 4/4 PASS. Full suite atac48cb7on macOS / Python 3.9 / herdr 0.9.3 with WORKSPACE_ROOT set to an empty sentinel: 1751 passed / 0 failed / 38 skipped; the sentinel stayed empty 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
8.4 KiB
Review 42f6a97d — Issue #17 follow-up, inherited WORKSPACE_ROOT
Head ac48cb78617af88584db529b5766acf02f4af73c (tree b043034eda891bd83b547dcb34d7422a6977b89d) closes the two checkout run_loop.sh writers left at 8b47cc7. The opt-in root is in place, the new nodes are in the isolation test, and both required pytest passes are green.
Snapshot /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261008T133858Z-9e7f5b/3 only. Base 6b00b6e06f55299cb60f156ccef09ede4bae5bdd (control-plane main, still that commit, porcelain empty). Branch commits: d198dd5, 8b47cc7, ac48cb7. Seventeen files, +221/−18, all under tests/. git diff --check 6b00b6e..HEAD 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. lib.sh:2050 uses AGENT_PYTHON_BIN when that path is executable. The suite set AGENT_PYTHON_BIN to the worktree interpreter and put its bin first on PATH.
What ac48cb7 adds
tests/test_o1_rebuttal.py:12 opts the module in with pytestmark = pytest.mark.usefixtures("own_workspace_root"). test_v3_rebut_option_parsing_and_budget (:69-76) still runs the checkout run_loop.sh twice (--help, and --max-rebut abc) with no env argument. The mark sets WORKSPACE_ROOT to tmp_path/.mam-test-state/workspace before the body, and a subprocess with no env inherits that value. run_loop.sh:17 sources lib.sh before the option parser, so the shim is written under the test directory.
tests/test_o2_race_free_lock.py:198 puts the same fixture on test_o2_12_run_loop_exits_on_lock_failure only. That test copies os.environ after the fixture's setenv (:204-206) and runs the checkout run_loop.sh. The other tests in the file source loop_lock.sh, which pulls in lib_int.sh and does not source lib.sh. test_o2_14 (:230) is bash -n.
tests/test_issue17_workspace_root_isolation.py:41-42 adds both node ids to W1:
tests/test_o2_race_free_lock.py::test_o2_12_run_loop_exits_on_lock_failuretests/test_o1_rebuttal.py::test_v3_rebut_option_parsing_and_budget
The comment on the o2 mark says a module-wide mark would add an entry to test_o2_7's tmp_path. test_o2_7 (:130) asserts list(tmp_path.glob("*")) == [], and Path.glob("*") skips dot names, so .mam-test-state would not show up. The function-level mark is still the right scope: only test_o2_12 executes run_loop.sh.
d198dd5 and 8b47cc7 are unchanged by this commit. tests/conftest.py:181-192 is the opt-in fixture (a temp workspace, not autouse). tests/test_issue17_item4_decisions.py:224-233 drops an inherited WORKSPACE_ROOT and sets the helper's tmp_path. Unset would make lib.sh:26 derive the checkout that contains the sourced script; lib.sh:3088 calls _init_herdr_isolation (lib.sh:143), which rewrites $WORKSPACE_ROOT/.mam/shim/herdr. pytest_configure (tests/conftest.py:144-146) drops WORKSPACE_ROOT only when its real path is a different directory, so a value that names this checkout survives collection. A test that also requests mam_sandbox keeps the sandbox root, because that fixture runs later. The orc-onboard tests request mam_sandbox and passed in the suite below.
The module mark is on test_o1_rebuttal.py, test_b19_headless_reconcile_fixes.py, test_b8_send_keys_verification.py, test_issue6_fail_closed.py, test_version_consistency.py, test_loop_cli.py, test_issue5_phase3_loop.py, test_issue5_phase4_runtime.py, test_o3_scoped_guard.py, test_issue25_plan_reuse.py, and test_orc_onboard.py.
Requirement 3 — the unfixed command writes the sentinel
Product run_loop.sh is unchanged, so the command test_v3 used to run is the pre-fix case. With WORKSPACE_ROOT=/tmp/42f6pre/sentinel (the directory existed) and the inherited HERDR_* / MAM_* / MQTT_* / SKS_* bindings removed:
bash <snapshot>/run_loop.sh --help exited 1 (usage) and left .mam, .mam/shim, and .mam/shim/herdr under that sentinel. The snapshot's .mam/shim/herdr stayed absent. The snapshot copy of run_loop.sh is mode 644; invoking it as the program raises PermissionError. The probe and the tests call it through bash.
W1 and W2, and test_c_helpers_write_nothing_to_an_inherited_workspace, passed inside both suites below. W1 is the check that still names the checkout: it sets WORKSPACE_ROOT to a copy of this tree, conftest keeps that value, and the copy must grow no .mam.
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/42f6A.n4k8Qw and /tmp/42f6B.p2m7Lx. Neither path contains mam-review.
(a) WORKSPACE_ROOT unset. The 16 modified test modules plus tests/test_issue33_budget.py, tests/test_issue5_phase1_delegation.py, and tests/test_tier1_unit.py: 675 passed in 836.91s, exit 0.
(b) WORKSPACE_ROOT=/tmp/42f6sent.t8c3Vr (the directory existed). The 16 modified modules: 522 passed in 672.98s, 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 shows that nothing wrote there before collection. The runs that keep a checkout-named root are W1 and the item-4 test, which monkeypatches a sentinel after configure. Both passed inside (a) and (b). The +32 tests against the previous head's 643 / 490 are the two modules this commit brought into the run.
After the pair: snapshot porcelain empty, snapshot .mam/shim/herdr absent, control-plane main still 6b00b6e with empty porcelain, control-plane shim mtime 1791482611 size 45360 (same before and after), ~/.config/herdr/config.toml mtime 1784456394, directory mtime 1791382452.
Survey — same pattern, already pinned
These execute lib.sh, run_loop.sh, or a skill script that sources lib.sh, and they already give that shell a temp root, or they do not execute the source. They stay as they are. No remaining writer runs the checkout lib.sh / run_loop.sh / wrapper on an inherited checkout root.
| Location | Why the checkout shim is not the target |
|---|---|
test_issue17_item4_decisions.py:224 |
_env sets WORKSPACE_ROOT to the helper tmp |
test_issue21_worktree_prereqs.py:403 |
test_t2_8 sets WORKSPACE_ROOT to mam_sandbox; _mutate sets the temp tree |
test_workspace_scope.py:32 |
subprocess env sets the test workspace |
test_issue18_liveness_contract.py:58 |
_env sets the helper cwd |
test_issue21_phase2_loop_a.py:58 |
sources a copy with WORKSPACE_ROOT set to that root |
test_issue21_loop_b.py:236 |
sets WORKSPACE_ROOT to tmp_path before run_loop.sh |
test_layout.py:318 |
the script exports the test workspace, then sources lib.sh |
test_issue20_state_isolation.py:201 |
sets WORKSPACE_ROOT to the temp repo the test is about |
test_issue19_subscriber_daemon_lifecycle.py:57 |
sources the sandbox copy; callers use mam_sandbox |
test_issue34_job_tree.py:589 |
sources the sandbox copy after mam_sandbox has set the root |
test_tier3_integration.py:286 |
sources the sandbox copy inside a mam_sandbox test; :337 is a debug string that also sets the temp root |
test_b7_diff_untracked.py:166 |
sources diff_collect.sh, which sources lib_int.sh (diff_collect.sh:14) |
test_o2_race_free_lock.py:10 and :231 |
loop_lock.sh sources lib_int.sh; bash -n does not execute run_loop.sh |
test_issue5_phase2a_loop.py:754 |
the source line is text inside an assertion |
test_deploy_freshness.py:473 |
reads run_loop.sh as text |
test_issue6_fail_closed.py:1247 sources the checkout lib.sh with the process environment. The module mark supplies the temp root, and that node is in W1. test_issue21_acceptance.py runs a run_loop.sh inside a temp git repo. test_issue33_budget.py, test_issue36_prompt_transport.py, test_issue6_turn_handshake.py, test_issue14_delivery_ack.py, test_issue26_envfile.py, test_issue26_ints.py, and test_c1_tui_readiness.py set a temp root in the helper env. test_issue10_13_purge.py and test_issue9_server_env.py / test_issue45_restore.py run through mam_sandbox or the rig, which has already set the root. test_issue30_stall_watch.py reaches the wrapper through mam_sandbox or test_issue18._submit.
[VERDICT: PASS]