test(issue-17): every test that sources lib.sh does it under its own WORKSPACE_ROOT (review grok, codex)

d198dd5 fixed one helper; the same defect was in eleven more modules
(reviews grok and codex R1/R2): their shells inherited the process
environment, so each `source lib.sh` rewrote <WORKSPACE_ROOT>/.mam/shim/herdr
- the checkout's own when the variable is unset or names this checkout (which
pytest_configure keeps), another workspace's under --noconftest.

- conftest: own_workspace_root, an opt-in fixture setting WORKSPACE_ROOT to
  <tmp_path>/.mam-test-state/workspace. Not autouse: runner.py and
  mqtt_common read WORKSPACE_ROOT in-process, and some tests unset it on
  purpose. mam_sandbox and explicit envs run later and still win.
- pytestmark usefixtures("own_workspace_root") in test_version_consistency,
  test_b19_headless_reconcile_fixes, test_b8_send_keys_verification,
  test_issue6_fail_closed, test_loop_cli, test_issue5_phase3_loop,
  test_issue5_phase4_runtime, test_o3_scoped_guard, test_issue25_plan_reuse,
  test_orc_onboard (their shells inherit os.environ, many with no env at all).
- test_orc_onboard (R2): SCRUBBED_ENV is taken at import and no longer
  carries WORKSPACE_ROOT; every call builds _scrubbed_env(root) with the root
  of the call (ws / cwd / mam_sandbox, else this test's).
- test_workspace_scope (unittest) and test_issue21_worktree_prereqs._mutate
  pin the root they already create.

Tests (test_issue17_workspace_root_isolation.py): W1 copies the checkout and
runs one test of each of those modules there with WORKSPACE_ROOT naming the
copy - the copy gets no .mam; W2 imports test_orc_onboard under a sentinel
root, then calls it under another - the sentinel stays empty. At d198dd5 both
fail (the copy gets .mam/shim/herdr; the sentinel gets it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HCGpJXhSZasJLU38ksDghe
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-09 00:22:54 +09:00
1 parent d198dd50aa
commit 8b47cc76ee
14 files changed
+176 -16

No files matched your search

+26 -14
View File
@@ -7,19 +7,31 @@ import shutil
import pytest
from pathlib import Path
# Issue #17: shells started here inherit a WORKSPACE_ROOT of this test's own
# (lib.sh would otherwise rewrite the checkout's .mam/shim/herdr).
pytestmark = pytest.mark.usefixtures("own_workspace_root")
REPO_ROOT = Path(__file__).parent.parent
LIB_SH = REPO_ROOT / ".agents" / "skills" / "lib.sh"
ONBOARD_SH = REPO_ROOT / ".agents" / "skills" / "multi-agent-mux-orc-onboard" / "scripts" / "orc_onboard.sh"
RECONCILE_SH = REPO_ROOT / ".agents" / "skills" / "multi-agent-mux-monitor" / "scripts" / "reconcile.sh"
SCRUBBED_ENV = dict(os.environ)
for k in ["CLAUDE_CODE_SESSION_ID", "ANTIGRAVITY_CONVERSATION_ID", "HERMES_SESSION_ID", "MAM_ORCHESTRATOR_UUIDS"]:
for k in ["CLAUDE_CODE_SESSION_ID", "ANTIGRAVITY_CONVERSATION_ID", "HERMES_SESSION_ID", "MAM_ORCHESTRATOR_UUIDS",
"WORKSPACE_ROOT"]:
SCRUBBED_ENV.pop(k, None)
SCRUBBED_ENV["PYTHONPATH"] = f"{LIB_SH.parent}:{SCRUBBED_ENV.get('PYTHONPATH', '')}"
def _scrubbed_env(root=None) -> dict:
"""SCRUBBED_ENV (taken at import) with the WORKSPACE_ROOT of now: `root`,
else this test's (own_workspace_root / mam_sandbox) - never the one the
module was imported under (Issue #17: lib.sh rewrites <it>/.mam/shim/herdr)."""
return dict(SCRUBBED_ENV, WORKSPACE_ROOT=str(root or os.environ["WORKSPACE_ROOT"]))
def run_onboard(args, env=None, cwd=None):
run_env = dict(SCRUBBED_ENV)
run_env = _scrubbed_env(cwd)
if cwd:
run_env["AGENT_SESSIONS_YAML"] = f"{cwd}/.mam/agent-sessions.yaml"
run_env["YAML_PATH"] = f"{cwd}/.mam/agent-sessions.yaml"
@@ -31,7 +43,7 @@ def run_onboard(args, env=None, cwd=None):
def run_find_uuid(ws, agent, target="", env=None):
run_env = dict(SCRUBBED_ENV)
run_env = _scrubbed_env(ws)
run_env["AGENT_SESSIONS_YAML"] = f"{ws}/.mam/agent-sessions.yaml"
run_env["YAML_PATH"] = f"{ws}/.mam/agent-sessions.yaml"
run_env["HOME_DIR"] = ws
@@ -44,7 +56,7 @@ def run_find_uuid(ws, agent, target="", env=None):
def run_verify_uuid(ws, agent, uuid, row=None, mode="discover", env=None):
run_env = dict(SCRUBBED_ENV)
run_env = _scrubbed_env(ws)
run_env["AGENT_SESSIONS_YAML"] = f"{ws}/.mam/agent-sessions.yaml"
run_env["YAML_PATH"] = f"{ws}/.mam/agent-sessions.yaml"
run_env["HOME_DIR"] = ws
@@ -62,7 +74,7 @@ def dump_state(mam_sandbox, state):
env = {"AGENT_SESSIONS_YAML": yaml_path, "YAML_PATH": yaml_path, "HOME_DIR": str(mam_sandbox)}
state_str = json.dumps(state)
cmd = f"source '{LIB_SH}' && atomic_dump_yaml '{yaml_path}' <<'PYEOF'\nd.update({state_str})\nPYEOF"
subprocess.run(["bash", "-c", cmd], env={**SCRUBBED_ENV, **env}, check=True)
subprocess.run(["bash", "-c", cmd], env={**_scrubbed_env(mam_sandbox), **env}, check=True)
return env
@@ -307,7 +319,7 @@ def test_o18_autodetect_via_env(mam_sandbox):
}
# Run inside subshell where command line matches claude
cmd = f"export CLAUDE_CODE_SESSION_ID='{orc_uuid}' && exec -a claude bash '{ONBOARD_SH}'"
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, env={**SCRUBBED_ENV, **env}, cwd=str(mam_sandbox))
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, env={**_scrubbed_env(), **env}, cwd=str(mam_sandbox))
assert res.returncode == 0
res_list = run_onboard(["--list"], env=env, cwd=str(mam_sandbox))
@@ -327,7 +339,7 @@ def test_o20_validate_fails_on_non_list_orchestrator_uuids(mam_sandbox):
env = {"AGENT_SESSIONS_YAML": str(mam_sandbox / ".mam" / "agent-sessions.yaml"), "YAML_PATH": str(mam_sandbox / ".mam" / "agent-sessions.yaml"), "HOME_DIR": str(mam_sandbox)}
state_str = json.dumps(state)
cmd = f"source '{LIB_SH}' && atomic_dump_yaml '{env['YAML_PATH']}' <<'PYEOF'\nd.update({state_str})\nPYEOF"
res = subprocess.run(["bash", "-c", cmd], env={**SCRUBBED_ENV, **env}, capture_output=True, text=True)
res = subprocess.run(["bash", "-c", cmd], env={**_scrubbed_env(), **env}, capture_output=True, text=True)
assert res.returncode != 0
assert "VALIDATE" in res.stderr or "VALIDATE" in res.stdout
@@ -339,7 +351,7 @@ def test_o21_validate_fails_on_duplicate_orchestrator_uuids(mam_sandbox):
env = {"AGENT_SESSIONS_YAML": str(mam_sandbox / ".mam" / "agent-sessions.yaml"), "YAML_PATH": str(mam_sandbox / ".mam" / "agent-sessions.yaml"), "HOME_DIR": str(mam_sandbox)}
state_str = json.dumps(state)
cmd = f"source '{LIB_SH}' && atomic_dump_yaml '{env['YAML_PATH']}' <<'PYEOF'\nd.update({state_str})\nPYEOF"
res = subprocess.run(["bash", "-c", cmd], env={**SCRUBBED_ENV, **env}, capture_output=True, text=True)
res = subprocess.run(["bash", "-c", cmd], env={**_scrubbed_env(), **env}, capture_output=True, text=True)
assert res.returncode != 0
@@ -349,7 +361,7 @@ def test_o22_validate_fails_on_empty_string_orchestrator_uuid(mam_sandbox):
env = {"AGENT_SESSIONS_YAML": str(mam_sandbox / ".mam" / "agent-sessions.yaml"), "YAML_PATH": str(mam_sandbox / ".mam" / "agent-sessions.yaml"), "HOME_DIR": str(mam_sandbox)}
state_str = json.dumps(state)
cmd = f"source '{LIB_SH}' && atomic_dump_yaml '{env['YAML_PATH']}' <<'PYEOF'\nd.update({state_str})\nPYEOF"
res = subprocess.run(["bash", "-c", cmd], env={**SCRUBBED_ENV, **env}, capture_output=True, text=True)
res = subprocess.run(["bash", "-c", cmd], env={**_scrubbed_env(), **env}, capture_output=True, text=True)
assert res.returncode != 0
@@ -367,7 +379,7 @@ if h1 == h2:
sys.exit(1)
PYEOF
"""
res = subprocess.run(["bash", "-c", cmd], env={**SCRUBBED_ENV, "AGENT_SESSIONS_YAML": yaml_path, "YAML_PATH": yaml_path}, capture_output=True, text=True)
res = subprocess.run(["bash", "-c", cmd], env={**_scrubbed_env(), "AGENT_SESSIONS_YAML": yaml_path, "YAML_PATH": yaml_path}, capture_output=True, text=True)
assert res.returncode == 0
@@ -438,7 +450,7 @@ def test_o27_ignore_different_family_env_variable(mam_sandbox):
"ANTIGRAVITY_CONVERSATION_ID": "72d2d251-5a06-486b-92e5-7e46a7a80d2e"
}
cmd = f"export ANTIGRAVITY_CONVERSATION_ID='72d2d251-5a06-486b-92e5-7e46a7a80d2e' && exec -a claude bash '{ONBOARD_SH}'"
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, env={**SCRUBBED_ENV, **env}, cwd=str(mam_sandbox))
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, env={**_scrubbed_env(), **env}, cwd=str(mam_sandbox))
assert res.returncode == 3
@@ -451,7 +463,7 @@ def test_o28_use_family_matched_env_variable(mam_sandbox):
"ANTIGRAVITY_CONVERSATION_ID": orc_uuid
}
cmd = f"export ANTIGRAVITY_CONVERSATION_ID='{orc_uuid}' && exec -a agy bash '{ONBOARD_SH}'"
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, env={**SCRUBBED_ENV, **env}, cwd=str(mam_sandbox))
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, env={**_scrubbed_env(), **env}, cwd=str(mam_sandbox))
assert res.returncode == 0
res_list = run_onboard(["--list"], env=env, cwd=str(mam_sandbox))
@@ -467,7 +479,7 @@ def test_o29_fresh_claude_session_detection(mam_sandbox):
"CLAUDE_CODE_SESSION_ID": orc_uuid
}
cmd = f"export CLAUDE_CODE_SESSION_ID='{orc_uuid}' && exec -a claude bash '{ONBOARD_SH}'"
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, env={**SCRUBBED_ENV, **env}, cwd=str(mam_sandbox))
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, env={**_scrubbed_env(), **env}, cwd=str(mam_sandbox))
assert res.returncode == 0
res_list = run_onboard(["--list"], env=env, cwd=str(mam_sandbox))
@@ -484,7 +496,7 @@ def test_o30_argv_precedes_env(mam_sandbox):
"CLAUDE_CODE_SESSION_ID": env_uuid
}
cmd = f"export CLAUDE_CODE_SESSION_ID='{env_uuid}' && exec -a claude bash '{ONBOARD_SH}' --uuid {argv_uuid}"
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, env={**SCRUBBED_ENV, **env}, cwd=str(mam_sandbox))
res = subprocess.run(["bash", "-c", cmd], capture_output=True, text=True, env={**_scrubbed_env(), **env}, cwd=str(mam_sandbox))
assert res.returncode == 0
res_list = run_onboard(["--list"], env=env, cwd=str(mam_sandbox))