Files
multi-agent-mux/.agents/reports/planner-reviewer-codex-01/report-9bc1f200.md
T
GodopuandClaude Opus 5.5 e0181ea9be release(v4.8.8): Issue #17 follow-up - archive the review reports and bump the lockstep version
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 in 8b47cc7.
Round 2 claude grok NOT PASS (test_o2_race_free_lock, test_o1_rebuttal
missed), agy codex PASS; fixed in ac48cb7. Round 3 (@ac48cb7) 4/4 PASS.
Full suite at ac48cb7 on 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
2026-10-09 05:01:05 +09:00

12 KiB

Issue #17 WORKSPACE_ROOT test isolation — Codex review

Job: 9bc1f200 Loop: loop-20261008T133858Z-9e7f5b Base: 6b00b6e06f Head: d198dd50aa Tree: 114ec21d3f Validation path: /var/folders/q_/68sxgw3j073dgjg8wl9s08vw0000gn/T/mam-review/loop-20261008T133858Z-9e7f5b/1

Conclusion

NOT PASS. The local _env fix and its new regression are correct, but task item 2 remains incomplete: equivalent unsafe test helpers are still present, including paths not protected by mam_sandbox and a cached environment that bypasses that fixture. These are independently reproduced isolation defects, not a verdict based on sandbox restrictions. Product-code changes are not required.

R1 — other helpers still write into an inherited workspace (P2)

Evidence:

  • tests/test_b19_headless_reconcile_fixes.py:124-134 copies os.environ and sources the checkout's lib.sh before replacing _init_herdr_isolation with a no-op. That replacement is too late: source-time bootstrap has already written the shim. Several callers, e.g. test_fullscreen_tip_is_not_a_blocking_dialog, do not request mam_sandbox.
  • tests/test_loop_cli.py:13-23 copies os.environ and executes the checkout's run_loop.sh. Its test_help_lists_creator_not_target_agent (:30) has no mam_sandbox. run_loop.sh:17 sources lib.sh even for --help.
  • lib.sh:26 defaults an absent WORKSPACE_ROOT to the checkout, and :144-152 creates .mam/shim and writes a shim temporary file. Thus merely deleting the inherited variable is insufficient.

Reproduction, performed only with temporary directories:

  1. Scrub inherited HERDR_, MAM_, MQTT_, SKS_ and control-plane bindings, set HOME to a temporary directory, and set WORKSPACE_ROOT to a new empty temporary sentinel.
  2. Import test_b19_headless_reconcile_fixes and call _run_lib_helpers('echo probe-ok').
  3. In a fresh sentinel, import test_loop_cli and call _run_loop(['--help']).
  4. Expected: sentinel remains empty. Actual: both create sentinel/.mam/shim/herdr; the helper exits 0 and the help invocation exits its normal 1.

A normal pytest session does not solve the unset case: tests/conftest.py:138-146 drops an external root at configure time, but _isolate_mam_state_dirs (:161-178) sets only registry/log/env-file paths. Only mam_sandbox (:215) pins WORKSPACE_ROOT. These callers do not use that fixture, so the checkout fallback remains. The state guard snapshots jobs and audit logs, not shim files (tests/_mam_state_guard.py:79-86).

Fix direction: establish a temporary per-test WORKSPACE_ROOT before every source/bootstrap call, preferably via the common function-scoped isolation fixture for ordinary runtime environment copies; preserve later explicit test overrides. Helpers intended for direct/--noconftest use should also build their environment from their own temporary path, as the revised Issue #17 helper does. Cover the candidates below, add sentinel checks, and audit intentional unset-workspace tests separately instead of changing their behavior blindly.

R2 — onboard's import-time environment bypasses later fixture isolation (P2)

Evidence: tests/test_orc_onboard.py:15 captures SCRUBBED_ENV at import; :22, :34, :47 and :65 reuse it. The helpers select temporary YAML/HOME_DIR paths but never set WORKSPACE_ROOT before sourcing the original LIB_SH (:41, :55, :64).

Reproduction:

  1. Set WORKSPACE_ROOT to an empty temporary sentinel before importing test_orc_onboard.
  2. After import, set os.environ['WORKSPACE_ROOT'] to a different temporary fixture workspace, as mam_sandbox would.
  3. Call run_find_uuid(str(fixture_workspace), 'claude') with no real user data or agent session.
  4. Expected: the sentinel stays empty and any bootstrap shim belongs to the fixture workspace. Actual: the cached sentinel receives .mam/shim/herdr, and the fixture workspace receives no shim.

With normal pytest's configure-time scrub, the cached dictionary instead lacks WORKSPACE_ROOT; later mam_sandbox updates still cannot change that dictionary, so lib.sh falls back to the checkout. This needs explicit treatment even if R1 is solved with a function-scoped autouse fixture.

Fix direction: preserve the intended removal of agent identity variables, but set WORKSPACE_ROOT explicitly from ws/cwd/mam_sandbox in every sourced-shell environment, including dump_state and the inline shell call sites. Alternatively build the scrubbed environment at invocation time after fixtures run, still pinning the temporary root. Add a regression where import-time and invocation-time roots differ.

Probe source: /tmp/9bc1f200-probes.py. All three reproduced leaked shims are under /private/tmp/9bc1f200-probe-z4ewh5mh; no real workspace was used as a sentinel.

Candidate audit

I searched tests for os.environ copies/comprehensions, WORKSPACE_ROOT, source/lib.sh, wrapper and run_loop.sh calls, then checked the caller fixtures and environment overrides.

Candidate / file:line Disposition and reason
test_issue17_item4_decisions.py:224 Fixed correctly: pins tmp_path; extra explicit overrides still apply last.
test_b19_headless_reconcile_fixes.py:124, :329, :369, :430 Unfixed; R1 helper and direct shell calls source original lib.sh without a temporary root.
test_loop_cli.py:13 Unfixed; R1. Later _loop_env at :91 pins a sandbox, but early CLI tests do not use it.
test_issue5_phase3_loop.py:145, :154, :172 Same unfixed bootstrap class: checkout run_loop.sh with ambient environment and no mam_sandbox.
test_issue5_phase4_runtime.py:126, :152 Same unfixed class in run_loop.sh help/freeze checks. tmp_path is available but not bound to WORKSPACE_ROOT.
test_o3_scoped_guard.py:376, :412, :458 Same unfixed class in checkout run_loop.sh invocations; --help also sources lib.sh. loop_lock-only snippets are a separate, non-shim path.
test_version_consistency.py:28, :131 Same unfixed class: version checks source original LIB_SH with implicit/copied environment, without mam_sandbox.
test_workspace_scope.py:22-30 Same unfixed class: unittest creates a temporary project but does not bind WORKSPACE_ROOT before two sources of original lib.sh.
test_b8_send_keys_verification.py:5-34 Same unfixed class: tmp_path argument is unused for workspace binding before source.
test_issue6_fail_closed.py:1220-1244 Same unfixed class: the direct source in the pinned-token test binds MAM_JOBS_DIR but not WORKSPACE_ROOT. Other _env-based cases in this file bind cwd explicitly.
test_orc_onboard.py:15-65 Unfixed cached environment; R2. A later mam_sandbox fixture alone does not protect it.
test_issue33_budget.py:43-47; test_issue14_delivery_ack.py:33-36; test_issue18_liveness_contract.py:55-58 No equivalent defect in these helpers: explicitly pin home/cwd as WORKSPACE_ROOT.
test_issue34_job_tree.py:51-64; test_issue36_prompt_transport.py:48-61; test_issue26_envfile.py:74-76; test_issue26_ints.py:123-124; test_issue30_stall_watch_stop.py:30-32 Explicitly pin temporary home/workspace. Later Issue #34 _run_lib users request mam_sandbox.
test_issue21_phase2_loop_a.py:49, :59 Explicit temporary root override.
test_issue21_worktree_prereqs.py:41-55 Removal is intentional for publisher-command tests that must operate without workspace bindings; actual _dry_submit callers at :92, :114, :143 supply temporary WORKSPACE_ROOT. Not a blanket reason to change this helper.
test_issue5_phase1_delegation.py:49; test_tier1_unit.py:14; tier2/3/4 helpers; test_issue19_subscriber_daemon_lifecycle.py:52 Ordinary helpers consume mam_sandbox's runtime environment; fixture :215 pins the workspace. Unlike onboard, these copies are made at invocation time.
test_issue9_server_env.py:84, :108; test_issue23_24_startup_path.py:36; test_a4_adapter_contract.py:558 Source/bootstrap callers use mam_sandbox; intentional server-marker tests retain that temporary workspace binding.
test_issue45_restore.py:55, :179 rig depends on mam_sandbox, then creates disposable HOME/server state.
test_issue45_herdr_config.py:302; test_issue36_prompt_transport.py:427 Python config CLI or diff_collect/lib_int paths; do not bootstrap lib.sh's shim.
test_deploy_layout.py:16 and deploy tests Installer uses explicit temporary installation targets; it does not source lib.sh for bootstrap.
test_issue5_phase2a_loop.py:750-757 The source invocation is a string for a static scanner test, not executed.
test_issue20_state_isolation.py:199-201 Explicit temporary WORKSPACE_ROOT for its source probe.

The unfixed candidates above are within the requested same-defect audit scope; they are not unrelated cleanup requests.

Local fix and verification

Setting WORKSPACE_ROOT to tmp_path, rather than deleting it, is the correct local choice because lib.sh derives its default from the library's location, not HOME or cwd. The new test validates the empty sentinel, unchanged checkout-shim mtime, expected shell workspace and actual shim in the test root.

The exact base _env function was loaded from git and substituted only in memory into the current test module. The new regression failed with "written into the sentinel" and the sentinel shim existed. With head _env it passed and no sentinel shim existed. This negative control changed no repository files.

  • Changed file, WORKSPACE_ROOT absent at pytest launch: 286 passed, 4 deselected in 262.11s; exit 0. Log: /tmp/9bc1f200-unset.log.
  • Same file, WORKSPACE_ROOT set to an existing external temporary sentinel: 286 passed, 4 deselected in 262.02s; exit 0. Log: /tmp/9bc1f200-sentinel.log.
  • Both runs used --noconftest, -p no:cacheprovider and separate external basetemp directories, with no plugin/fixture pinning WORKSPACE_ROOT. This tests the helper's fix itself.
  • At completion, /private/tmp/9bc1f200-unset-7yqs28zl/sentinel and /private/tmp/9bc1f200-sentinel-otbahzmr/sentinel each contained zero entries.
  • Supplementary tests: 13 broker-independent issue33_budget cases plus the wrapper mapping contract from test_issue5_phase1_delegation and the G10 mapping contract from test_tier1_unit: 15 passed, 7 deselected in 29.57s. Log: /tmp/9bc1f200-other.log. Only the two contract tests used a minimal temporary mam_sandbox fixture; this is not a full-suite pass claim for those two files.
  • Full broker-dependent suites were not executed in the restricted environment; the orchestrator retains their integration validation.
  • Independent negative-control regression and the three omitted-helper probes completed successfully, producing the results detailed above.
  • The changed Python file parses on Python 3.9; git diff --check passes. Final git status --porcelain was empty and HEAD/tree still matched the brief.

The four unreaped-owner cases were deselected because this sandbox blocks ps, as independently diagnosed in the earlier review. The brief permits network-free review where broker integration is blocked. Neither these omissions nor MQTT publication restrictions are the reason for NOT PASS.

Scope and lifecycle

Only tests/test_issue17_item4_decisions.py changed (36 insertions, 2 deletions). Product files, versions and branches are unchanged. All test runs and probes used the designated review path, Python 3.9 and native Bash 3.2; artifacts, test registries, HOME and all sentinel roots were temporary. Inherited control-plane/server variables were scrubbed. No .mam.env was exported, no real agent/server was contacted, and no reviewer source edits were made.

The requested started command was executed first; MQTT exhausted three attempts with Operation not permitted and exited 2. The requested completed command was also executed after saving the report; all three MQTT attempts failed with Operation not permitted and the command exited 2. The report is saved, but successful delivery of started/completed to the broker is not confirmed.

[VERDICT: NOT PASS]