"""Issue #72 (decision 2): resume ensures the monitor daemon, as create does. Only create_session.sh started the per-workspace subscriber daemon, so a session brought back by resume_session.sh had no monitor unless one already ran. Resume now calls the same `start_watchdog` with the control root as the workdir (Issue #21 A8: never a worktree --workspace's copy), on the spawn path and the already-running path, after the row is written. What is real: the shipped create/stop/resume scripts and the real start_watchdog -> lib_py ensure-daemon, against the mock herdr in a sandbox. What is not: the sandbox's monitor script is a stub that records how it was started and sleeps, so the daemon's `pgrep -f "bash --subscribe"` check sees it but nothing reconciles. Every stub is killed in teardown. """ import os import signal import subprocess import time import pytest CREATE = "multi-agent-mux-create/scripts/create_session.sh" STOP = "multi-agent-mux-stop/scripts/stop_session.sh" RESUME = "multi-agent-mux-resume/scripts/resume_session.sh" MONITOR_REL = os.path.join(".agents", "skills", "multi-agent-mux-monitor", "scripts", "reconcile.sh") def _script(root, rel): return str(root / ".agents" / "skills" / rel) def _install_stub_monitor(base, log): """Replace 's monitor with a stub that writes `pid|$0|cwd|args`.""" path = base / MONITOR_REL path.parent.mkdir(parents=True, exist_ok=True) path.write_text('#!/bin/bash\nprintf \'%%s|%%s|%%s|%%s\\n\' "$$" "$0" "$PWD" "$*" >> "%s"\n' 'sleep 60 &\nwait $!\n' % log) path.chmod(0o755) return str(path) def _starts(log): return [line.split("|") for line in log.read_text().splitlines()] if log.exists() else [] def _wait_starts(log, n, timeout=10.0): deadline = time.time() + timeout while time.time() < deadline and len(_starts(log)) < n: time.sleep(0.1) return _starts(log) @pytest.fixture def stub(mam_sandbox): """The control root's stub monitor; kills every stub process it recorded (the mam_sandbox reaper also sweeps anything under the sandbox).""" logs = [] def install(base, name): log = mam_sandbox / ("monitor-starts-%s.log" % name) logs.append(log) return _install_stub_monitor(base, log), log yield install for log in logs: for row in _starts(log): pid = int(row[0]) for target in subprocess.run(["pgrep", "-P", str(pid)], capture_output=True, text=True).stdout.split() + [str(pid)]: try: os.kill(int(target), signal.SIGKILL) except OSError: pass def _create(root, ws, name, stop): subprocess.run(["bash", _script(root, CREATE), "--workspace", ws, "--agent", "claude", "--role", "creator", "--session", name, "--no-onboard"], check=True, cwd=str(root), capture_output=True, text=True) if stop: subprocess.run(["bash", _script(root, STOP), "--session", name, "--agent", "claude"], check=True, cwd=str(root), capture_output=True, text=True) def _resume(root, ws, name): return subprocess.run(["bash", _script(root, RESUME), "--workspace", ws, "--agent", "claude", "--session", name], cwd=str(root), capture_output=True, text=True, timeout=120) def _assert_root_monitor_started(root, monitor, log, res): starts = _wait_starts(log, 1) assert len(starts) == 1, starts pid, script, cwd, args = starts[0] assert os.path.realpath(script) == os.path.realpath(monitor) assert os.path.realpath(cwd) == os.path.realpath(str(root)) assert args == "--subscribe --idle-timeout 0" assert "watchdog PID: %s" % pid in res.stdout, res.stdout def test_spawn_path_ensures_the_control_root_monitor(mam_sandbox, mock_herdr, mock_agents, stub, monkeypatch): root = mam_sandbox _create(root, str(root), "issue72-spawn", stop=True) monitor, log = stub(root, "root") monkeypatch.delenv("MAM_NO_SUBSCRIBER_DAEMON", raising=False) res = _resume(root, str(root), "issue72-spawn") assert res.returncode == 0, res.stdout + res.stderr assert "Successfully resumed" in res.stdout _assert_root_monitor_started(root, monitor, log, res) def test_already_running_path_ensures_the_monitor_once(mam_sandbox, mock_herdr, mock_agents, stub, monkeypatch): root = mam_sandbox _create(root, str(root), "issue72-alive", stop=False) monitor, log = stub(root, "root") monkeypatch.delenv("MAM_NO_SUBSCRIBER_DAEMON", raising=False) res = _resume(root, str(root), "issue72-alive") assert res.returncode == 0, res.stdout + res.stderr assert "already running" in res.stdout _assert_root_monitor_started(root, monitor, log, res) # A second resume finds the running daemon and starts no other. pid = _starts(log)[0][0] again = _resume(root, str(root), "issue72-alive") assert again.returncode == 0, again.stdout + again.stderr assert "watchdog PID: %s" % pid in again.stdout time.sleep(1) assert len(_starts(log)) == 1 @pytest.mark.parametrize("stop", [True, False], ids=["spawn", "already-running"]) def test_worktree_session_uses_the_control_root_monitor(mam_sandbox, mock_herdr, mock_agents, stub, monkeypatch, stop): """A session whose pane cwd (--workspace) is not the control root still gets the control root's monitor, never the copy in its own tree.""" root = mam_sandbox wt = root / "wt-issue72" wt.mkdir() name = "issue72-wt-" + ("spawn" if stop else "alive") _create(root, str(wt), name, stop=stop) monitor, log = stub(root, "root") _, wt_log = stub(wt, "worktree") monkeypatch.delenv("MAM_NO_SUBSCRIBER_DAEMON", raising=False) res = _resume(root, str(wt), name) assert res.returncode == 0, res.stdout + res.stderr _assert_root_monitor_started(root, monitor, log, res) assert _starts(wt_log) == [] def test_opt_out_starts_nothing_and_reports_a_running_daemon(mam_sandbox, mock_herdr, mock_agents, stub): """MAM_NO_SUBSCRIBER_DAEMON=1 (the sandbox default): resume starts no daemon; one that already runs is still found and reported.""" root = mam_sandbox assert os.environ.get("MAM_NO_SUBSCRIBER_DAEMON") == "1" _create(root, str(root), "issue72-optout", stop=True) monitor, log = stub(root, "root") res = _resume(root, str(root), "issue72-optout") # spawn path assert res.returncode == 0, res.stdout + res.stderr time.sleep(1) assert _starts(log) == [] pre = subprocess.Popen(["bash", monitor, "--subscribe", "--idle-timeout", "0"], cwd=str(root), stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, start_new_session=True) try: assert len(_wait_starts(log, 1)) == 1 res = _resume(root, str(root), "issue72-optout") # already-running path assert res.returncode == 0, res.stdout + res.stderr assert "watchdog PID: %d" % pre.pid in res.stdout, res.stdout time.sleep(1) assert len(_starts(log)) == 1 finally: pre.kill() pre.wait() def test_a_missing_monitor_warns_and_the_resume_still_succeeds(mam_sandbox, mock_herdr, mock_agents, monkeypatch): root = mam_sandbox _create(root, str(root), "issue72-nomon", stop=True) os.remove(str(root / MONITOR_REL)) monkeypatch.delenv("MAM_NO_SUBSCRIBER_DAEMON", raising=False) res = _resume(root, str(root), "issue72-nomon") assert res.returncode == 0, res.stdout + res.stderr assert "Successfully resumed" in res.stdout assert "WARNING: could not start the monitor daemon" in res.stderr