feat(issue-21): resume only at the original cwd; persist the control-plane binding on the already-running path; fail loudly when a worktree binding cannot be computed
- A11: resume refuses (exit 2) a --workspace other than the row's pane.cwd, and a pane.cwd that no longer exists (checked first: mam_abs_workspace returns a missing path unchanged). Before, claude/grok failed with a misleading "No saved session" and class A agents started a new conversation in the wrong directory. Rows without pane.cwd are not checked. - A12 (recommendation 1): create and resume no longer swallow a failed binding computation. A worktree (--workspace != control root) is refused with exit 2 and nothing spawned or recorded; the control root itself warns and records the row without control_plane. - A13 (recommendation 2): resume computes the binding before the herdr-alive check, so the already-running path records it too (update_yaml_resumed.sh fills a missing field only). Pane forwarding and the whitespace refusal stay on the spawn path. Tests W7-W9. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017R3Eg7MEpSWnm1oB9ZPP6G
This commit is contained in:
1 parent
fadd38cf56
commit
2679ddfffd
3 files changed
+138
-15
No files matched your search
@@ -176,3 +176,73 @@ def test_w11_reinstall_replaces_the_old_rule_and_keeps_user_lines(tmp_path):
|
||||
assert "user-one" in gi.splitlines() and "user-two" in gi.splitlines()
|
||||
for ledger in list(tgt.rglob("asset_hashes.txt")) + list(tgt.rglob("install_manifest.txt")):
|
||||
assert ".gitignore" not in ledger.read_text().split(), ledger
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# resume: original cwd only (A11), binding failures (A12), already running (A13)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _resume(root, ws, name, agent="claude", env=None):
|
||||
return _run(root, ("multi-agent-mux-resume", "scripts", "resume_session.sh"),
|
||||
"--workspace", str(ws), "--agent", agent, "--session", name, env=env)
|
||||
|
||||
|
||||
def test_w7_resume_only_at_the_original_cwd(mam_sandbox, mock_herdr, mock_agents):
|
||||
root = mam_sandbox
|
||||
here, there, gone = root / "here", root / "there", root / "gone"
|
||||
here.mkdir()
|
||||
there.mkdir()
|
||||
_mutate(root, f"""d['herdr_sessions'] = [
|
||||
{{'name': 'x-creator-claude', 'status': 'stopped', 'pane': {{'cwd': {str(here)!r}, 'cmd': 'claude'}},
|
||||
'claude_session_id_own': '11111111-2222-4333-8444-555555555555', 'session_id_source': 'assigned'}},
|
||||
{{'name': 'x-creator-agy', 'status': 'stopped', 'pane': {{'cwd': {str(here)!r}, 'cmd': 'agy'}}}},
|
||||
{{'name': 'y-creator-claude', 'status': 'stopped', 'pane': {{'cwd': {str(gone)!r}, 'cmd': 'claude'}}}}]""")
|
||||
since = len(_calls(mock_herdr))
|
||||
res = _resume(root, there, "x-creator-claude")
|
||||
assert res.returncode == 2 and f"--workspace '{here}'" in res.stderr
|
||||
res = _resume(root, there, "x-creator-agy", agent="agy")
|
||||
assert res.returncode == 2
|
||||
res = _resume(root, gone, "y-creator-claude")
|
||||
assert res.returncode == 2 and "no longer exists" in res.stderr
|
||||
assert _spawn_calls(mock_herdr, since) == [], "nothing was spawned in the wrong directory"
|
||||
|
||||
|
||||
def test_w8_binding_failure_refused_for_a_worktree_warned_for_the_control_root(mam_sandbox, mock_herdr, mock_agents):
|
||||
root = mam_sandbox
|
||||
Path(_skill(root, "lib_py", "control_plane.py")).write_text("raise SystemExit(1)\n")
|
||||
wt = root.parent / f"{root.name}-wt8"
|
||||
wt.mkdir()
|
||||
since = len(_calls(mock_herdr))
|
||||
create = ("multi-agent-mux-create", "scripts", "create_session.sh")
|
||||
res = _run(root, create, "--workspace", str(wt), "--agent", "claude", "--role", "creator", "--no-onboard")
|
||||
assert res.returncode == 2 and "cannot compute the control-plane binding" in res.stderr
|
||||
assert _rows(root) == [] and _spawn_calls(mock_herdr, since) == []
|
||||
res = _run(root, create, "--workspace", str(root), "--agent", "claude", "--role", "creator", "--no-onboard")
|
||||
assert res.returncode == 0 and "WARNING: control-plane binding unavailable" in res.stderr, res.stderr
|
||||
(row,) = _rows(root)
|
||||
assert "control_plane" not in row
|
||||
|
||||
_mutate(root, f"""d['herdr_sessions'].append({{'name': 'w8-creator-claude', 'status': 'stopped',
|
||||
'pane': {{'cwd': {str(wt)!r}, 'cmd': 'claude'}},
|
||||
'claude_session_id_own': '11111111-2222-4333-8444-555555555555', 'session_id_source': 'assigned'}})""")
|
||||
res = _resume(root, wt, "w8-creator-claude")
|
||||
assert res.returncode == 2 and "cannot compute the control-plane binding" in res.stderr
|
||||
|
||||
|
||||
def test_w9_already_running_resume_persists_the_binding(mam_sandbox, mock_herdr, mock_agents):
|
||||
root = mam_sandbox
|
||||
create = ("multi-agent-mux-create", "scripts", "create_session.sh")
|
||||
res = _run(root, create, "--workspace", str(root), "--agent", "claude", "--role", "creator", "--no-onboard")
|
||||
assert res.returncode == 0, res.stdout + res.stderr
|
||||
name = _rows(root)[0]["name"]
|
||||
_mutate(root, "d['herdr_sessions'][0].pop('control_plane', None)")
|
||||
res = _resume(root, root, name)
|
||||
assert res.returncode == 0 and "already running" in res.stdout, res.stdout + res.stderr
|
||||
row = _rows(root)[0]
|
||||
assert set(row["control_plane"]) == set(CP_KEYS)
|
||||
assert row["control_plane"]["workspace_root"] == os.path.realpath(root)
|
||||
|
||||
custom = {**row["control_plane"], "mam_jobs_dir": str(root / "custom-jobs")}
|
||||
_mutate(root, f"d['herdr_sessions'][0]['control_plane'] = {custom!r}")
|
||||
assert _resume(root, root, name).returncode == 0
|
||||
assert _rows(root)[0]["control_plane"] == custom, "an existing row binding is kept"
|
||||
Reference in new issue
Block a user