# Review 35f0c94f — Issue #17 follow-up, inherited WORKSPACE_ROOT Head `8b47cc76eed4582b1848b2eec529523a61497320` (tree `bfe5fcbbefbde9562334515b4a9803bee96f84fd`) closes the writers named in the previous review and the new isolation tests pass. `tests/test_o1_rebuttal.py:68` still runs the checkout `run_loop.sh` with the process environment, and that script sources `lib.sh` before it parses `--help`, so an agent pane whose `WORKSPACE_ROOT` names this checkout still gets `.mam/shim/herdr` rewritten. Snapshot `/var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261008T133858Z-9e7f5b/2` only. Base `6b00b6e06f55299cb60f156ccef09ede4bae5bdd` (control-plane main, unchanged). Branch commits: `d198dd5` then `8b47cc7`. Fifteen files, +212/−18, `git diff --check` clean. Product code under `.agents/skills` is unchanged. No version bump. Interpreter: worktree `.venv` Python 3.9.6, bash 3.2.57. `lib.sh` picks that interpreter when `AGENT_PYTHON_BIN` is set (`lib.sh:2050`); otherwise it falls through to `/usr/bin/python3`, which has neither `paho` nor `yaml`. ## What the branch does `tests/conftest.py:181-192` adds an opt-in fixture `own_workspace_root`. It creates `tmp_path/.mam-test-state/workspace` and sets `WORKSPACE_ROOT` there. It is not autouse, because in-process code also reads `WORKSPACE_ROOT`. Modules whose shells inherit the process environment opt in with `pytestmark = pytest.mark.usefixtures("own_workspace_root")`. On this pytest, a test that requests both the mark and `mam_sandbox` finishes with `mam_sandbox`'s value. A one-test probe (usefixtures plus a `mam_sandbox` parameter) left `os.environ["WORKSPACE_ROOT"]` as the sandbox path. The orc-onboard tests, which use `mam_sandbox` and pass that directory into `_scrubbed_env`, passed in the suite below, so a test that names its own root still keeps it. `tests/test_issue17_item4_decisions.py:224-233` drops an inherited `WORKSPACE_ROOT` and sets the helper's `tmp_path`. Unset is the wrong fix: `lib.sh:26` derives the checkout that contains the script, and `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 checkout, so a value that names this checkout survives collection. That is why the item-4 helpers missed the autouse state fixture: they never request `mam_sandbox`. `tests/test_orc_onboard.py:19-30` still copies `os.environ` at import, and that copy now drops `WORKSPACE_ROOT`. Every source path rebuilds the env at call time from the current root (`_scrubbed_env`). `tests/test_issue21_worktree_prereqs.py:547` sets `WORKSPACE_ROOT` to the temp tree inside `_mutate`. `tests/test_workspace_scope.py:32` sets it to that test's workspace. The same mark is on `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 new tests fail on the pre-fix code `tests/test_issue17_workspace_root_isolation.py` adds W1 and W2. W1 copies the checkout, runs one node from each previously inheriting module with `WORKSPACE_ROOT` naming that copy (conftest keeps a root that names the checkout), and requires the copy to have no `.mam`. W2 imports `test_orc_onboard` while `WORKSPACE_ROOT` is a sentinel, then points the process env at a later directory and calls `run_find_uuid`. Pre-fix probe, snapshot `lib.sh`, outside the tree: - Base `tests/test_orc_onboard.py` from `6b00b6e`, loaded from a temp tree whose `.agents` is the snapshot's, imported with `WORKSPACE_ROOT` set to a sentinel, then `os.environ` pointed at another directory and `run_find_uuid` called: the sentinel received `.mam`, `.mam/shim`, `.mam/shim/herdr`. The later directory received nothing. Return code 0. - The same probe against the head module: the sentinel stayed empty, and the later directory received `.mam/shim/herdr`. Return code 0. `bash run_loop.sh --help` with `WORKSPACE_ROOT` set to `/tmp/35f0o1sent.q8w2` also wrote `.mam/shim/herdr` there (usage exits 1). Source is `run_loop.sh:17`, before the option parser. The snapshot shim was absent before and after that probe. W1 and W2 are in the passing suite below. The item-4 sentinel test `test_c_helpers_write_nothing_to_an_inherited_workspace` is in the same file as the `_env` fix and passed with it. ## Requirement 4 — suite `AGENT_PYTHON_BIN` and `PATH` pointed at the worktree `.venv`. Inherited `HERDR_*`, `MAM_*`, `MQTT_*`, `SKS_*`, and `WORKSPACE_ROOT` were unset in the parent. `PYTHONDONTWRITEBYTECODE=1`, `-p no:cacheprovider`. Basetemp directories are under `/tmp` and do not contain `mam-review`. (a) `WORKSPACE_ROOT` unset, basetemp `/tmp/35f0C.m2k8Qn`, the 14 modified test modules plus `tests/test_issue33_budget.py`, `tests/test_issue5_phase1_delegation.py`, and `tests/test_tier1_unit.py`: **643 passed** in 837.78s, exit 0. (b) `WORKSPACE_ROOT=/tmp/35f0sent2.w9c3Tb` (the directory existed), basetemp `/tmp/35f0D.h6p4Js`, the 14 modified modules: **490 passed** in 672.56s, exit 0. The sentinel was empty afterwards. `pytest_configure` removes a foreign `WORKSPACE_ROOT` before collection, so an outer sentinel stays empty even if a helper is wrong. The runs that keep a checkout-named root are W1 (it sets `WORKSPACE_ROOT` to the copied checkout) and the item-4 test (it monkeypatches a sentinel after configure). Both passed inside (a) and (b). A first pair of runs without `AGENT_PYTHON_BIN` reported 22 failed / 621 passed and 22 failed / 468 passed. Those failures are `/usr/bin/python3` missing `paho` and `yaml` (`mqtt_common.py` and `atomic_yaml.py` via `_delegate_py_bin`). The same nodes pass once `AGENT_PYTHON_BIN` is the worktree interpreter, which is the command the brief specifies. They are not a defect of `8b47cc7`. After both pairs: snapshot porcelain empty, snapshot `.mam/shim/herdr` absent, control-plane main still `6b00b6e` with empty porcelain, control-plane shim mtime `1791475620`, `~/.config/herdr/config.toml` mtime `1784456394`, directory mtime `1791382452`. ## The writer this head leaves in place `tests/test_o1_rebuttal.py:68` and `:72` (`test_v3_rebut_option_parsing_and_budget`) run `bash` on the checkout `run_loop.sh` (`--help`, and `--max-rebut abc`) with no `env`. The module has no `own_workspace_root` mark. `run_loop.sh:17` sources `lib.sh` before option parsing, and `lib.sh:3088` rewrites `$WORKSPACE_ROOT/.mam/shim/herdr`. With the variable unset, `lib.sh:26` derives this checkout. W1's node list does not name this test, so the new regression does not catch it. Add `pytestmark = pytest.mark.usefixtures("own_workspace_root")` to `tests/test_o1_rebuttal.py`, the same opt-in `test_loop_cli.py` uses, and add `tests/test_o1_rebuttal.py::test_v3_rebut_option_parsing_and_budget` to `NODES` in `tests/test_issue17_workspace_root_isolation.py`. Passing `WORKSPACE_ROOT` set to a temp directory on those two `subprocess.run` calls is the same fix at the call site. ## 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. | 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:547` | `_mutate` sets the temp tree; `test_t2_8` sets `mam_sandbox` | | `test_workspace_scope.py:32` | subprocess env sets the test workspace | | `test_issue33_budget.py:47` | helper home | | `test_issue18_liveness_contract.py:58` | helper cwd; `_submit` uses that env | | `test_issue36_prompt_transport.py:61` and `:537` | helper home, and the shell loop sets `ctl` | | `test_issue21_phase2_loop_a.py:49` | temp root | | `test_issue21_loop_b.py:236` | `tmp_path` | | `test_issue6_turn_handshake.py:29` | cwd | | `test_issue14_delivery_ack.py:36` | cwd | | `test_issue26_envfile.py:76`, `test_issue26_ints.py:124` | helper home | | `test_c1_tui_readiness.py:19` | temp directory | | `test_layout.py:318` | the script exports the test workspace | | `test_issue19_subscriber_daemon_lifecycle.py:138` | the only `_run_helper` caller uses `mam_sandbox` | | `test_issue34_job_tree.py:590` | copies the process env after `mam_sandbox` has set the root | | `test_issue10_13_purge.py:149` | `_run_onboard` callers request `mam_sandbox`, so the copied env already names the sandbox | | `test_issue20_state_isolation.py:201` | sets `WORKSPACE_ROOT` to the temp repo the test is about | | `test_issue9_server_env.py`, `test_issue45_restore.py` | callers use `mam_sandbox` / `rig`, which has already set the root | | `test_issue30_stall_watch.py:124` | `_env` itself does not assign the root; wrapper calls go through `mam_sandbox` or `test_issue18._submit` | | `test_o2_race_free_lock.py:229` | `bash -n` does not execute `run_loop.sh` | | `test_issue5_phase2a_loop.py:754` | the `source` line is text inside an assertion | | `test_tier3_integration.py:337` | the `source` is a debug string, and that env sets the temp root | | `test_deploy_freshness.py:473` | reads `run_loop.sh` as text | Marked modules listed above are covered by `own_workspace_root`. `test_issue21_acceptance.py` runs a `run_loop.sh` inside a temp git repo from its `ctl` fixture. [VERDICT: NOT PASS]