fix(update): an unlistable jobs directory refuses; the rowless-agent hint names its server (Issue #72, review R2, R3)
R2: glob() hid a listing error, so an unreadable .mam/jobs (or MAM_JOBS_DIR, or another row's jobs dir) counted as empty and the update installed over an open job. Records are now listed with os.listdir; a directory that cannot be listed, or stat'ed, is a blocker with its path. R3: a discovered agent with no row was told to run stop with --session only; stop then looked on the workspace's server, not the one the agent was found on. The hint now carries --herdr-session <server>, quoted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e3394655b3
commit
ba75718ac3
3 files changed
+96
-11
No files matched your search
@@ -45,6 +45,15 @@ elif a[:2] == ["agent", "list"]:
|
||||
if spec.get("fail") == "agent-list:" + server:
|
||||
sys.exit(1)
|
||||
print(json.dumps({"result": {"agents": spec.get("agents", {}).get(server, [])}}))
|
||||
elif a[:2] == ["agent", "get"]:
|
||||
hit = [x for x in spec.get("agents", {}).get(server, []) if a[2] in (x.get("name"), x.get("pane_id"))]
|
||||
if not hit:
|
||||
sys.exit(1)
|
||||
print(json.dumps({"result": {"agent": hit[0]}}))
|
||||
elif a[:2] == ["pane", "close"]:
|
||||
spec.setdefault("agents", {})[server] = [x for x in spec.get("agents", {}).get(server, [])
|
||||
if x.get("pane_id") != a[2]]
|
||||
json.dump(spec, open(os.environ["FAKE_HERDR_SPEC"], "w"))
|
||||
'''
|
||||
|
||||
STUB_MONITOR = r'''#!/usr/bin/env bash
|
||||
@@ -366,6 +375,28 @@ def test_refuses_a_rowless_agent_on_any_running_server(box):
|
||||
assert '"down"' not in calls
|
||||
|
||||
|
||||
def test_the_rowless_agent_hint_stops_it_on_the_server_it_was_found_on(box):
|
||||
"""R3: the printed stop command names the server; without it stop looks on
|
||||
the workspace's server, where a same-named agent runs outside the workspace."""
|
||||
write_rows(box.ws, [])
|
||||
box.herdr(sessions={"default": True, "unrecorded-server": True},
|
||||
agents={"unrecorded-server": [{"name": "stray", "pane_id": "w3:p1", "cwd": str(box.ws / "sub")}],
|
||||
"default": [{"name": "stray", "pane_id": "w1:p1", "cwd": str(box.root / "elsewhere")}]})
|
||||
res = box.update()
|
||||
assert res.returncode == 1, res.stdout + res.stderr
|
||||
line = next(ln for ln in res.stderr.splitlines() if "herdr agent stray" in ln)
|
||||
cmd = line.split("stop it: ", 1)[1]
|
||||
assert "--herdr-session unrecorded-server" in cmd, cmd
|
||||
stop = subprocess.run(["bash", "-c", cmd], cwd=str(box.ws), env=box.env, stdin=subprocess.DEVNULL,
|
||||
capture_output=True, text=True, timeout=120)
|
||||
assert stop.returncode == 0, stop.stdout + stop.stderr
|
||||
agents = json.loads(box.spec.read_text())["agents"]
|
||||
assert agents["unrecorded-server"] == []
|
||||
assert [x["pane_id"] for x in agents["default"]] == ["w1:p1"], "the same name elsewhere is left alone"
|
||||
res = box.update()
|
||||
assert res.returncode == 0, res.stdout + res.stderr
|
||||
|
||||
|
||||
def test_an_agent_outside_the_workspace_does_not_refuse(box):
|
||||
box.herdr(agents={"default": [{"name": "other", "pane_id": "w1:p2", "cwd": str(box.root / "elsewhere")}]})
|
||||
res = box.update()
|
||||
@@ -406,6 +437,34 @@ def test_open_jobs_refuse_and_terminal_or_dry_run_do_not(box, tmp_path):
|
||||
del box.env["MAM_JOBS_DIR"]
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.geteuid() == 0, reason="root reads a mode-000 directory")
|
||||
def test_a_jobs_directory_that_cannot_be_listed_refuses(box, tmp_path):
|
||||
"""R2: an unreadable .mam/jobs (or MAM_JOBS_DIR under an unreadable parent)
|
||||
is not an empty registry."""
|
||||
jobs = box.mam / "jobs"
|
||||
write_job(jobs, "hidden1", status="running")
|
||||
before = snapshot(box.ws)
|
||||
jobs.chmod(0)
|
||||
try:
|
||||
res = box.update()
|
||||
finally:
|
||||
jobs.chmod(0o755)
|
||||
assert_refused_unchanged(box, before, res, "jobs directory %s cannot be listed" % jobs)
|
||||
(jobs / "hidden1.json").unlink()
|
||||
|
||||
parent = tmp_path / "locked"
|
||||
write_job(parent / "jobs", "env1", status="running")
|
||||
box.env["MAM_JOBS_DIR"] = str(parent / "jobs")
|
||||
before = snapshot(box.ws)
|
||||
parent.chmod(0)
|
||||
try:
|
||||
res = box.update()
|
||||
finally:
|
||||
parent.chmod(0o755)
|
||||
assert_refused_unchanged(box, before, res, "jobs directory %s cannot be listed" % (parent / "jobs"))
|
||||
del box.env["MAM_JOBS_DIR"]
|
||||
|
||||
|
||||
def test_other_control_planes_count_only_jobs_addressed_to_this_registry(box, tmp_path):
|
||||
other = tmp_path / "other-plane" / "jobs"
|
||||
write_job(other, "theirs", status="running", session="herdr:not-ours")
|
||||
|
||||
Reference in new issue
Block a user