fix(issue-50): review findings of PR #56 on macOS

- admission: a `submit --dry-run` record is marked dry_run and is not an
  open job of its session (review 1e25cf2f F1)
- create: the monitor-row adoption requires the row's server to be the one
  this create spawned on (review 1e25cf2f F2)
- pane lock: retry only on contention (BlockingIOError); the docs say the
  flock lasts while any fd-9 holder (the claim shell and the shim) lives
  (review 66680d9d)
- tests on macOS: real-herdr box HOMEs under /tmp (sun_path is 104 bytes),
  resolved paths, the fake claude reads non-canonical input (MAX_CANON 1024)
  and the pane is read unwrapped; lock holders exec their sleep so killing
  them frees the lock; the adoption tests name the server

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VCgu9Fa9iT7TbJUSzdq4Ff
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-12 09:03:08 +09:00
1 parent 320aa9f7a0
commit a9ffedac36
10 files changed
+75 -19

No files matched your search

+11 -6
View File
@@ -28,7 +28,7 @@ if str(SKILLS) not in sys.path:
from lib_py import pane_lock # noqa: E402
# a bash holder, as the shim is: fd 9 on the file, the lock taken by the helper, then it sleeps
HOLDER = 'exec 9>>"$1"; python3 -m lib_py.pane_lock acquire --fd 9; echo held; sleep "${2:-600}"'
HOLDER = 'exec 9>>"$1"; python3 -m lib_py.pane_lock acquire --fd 9; echo held; exec sleep "${2:-600}"'
def _holder(path: Path, seconds: str = "600", **env) -> subprocess.Popen:
@@ -133,9 +133,12 @@ def test_p5_the_wait_knob_is_a_decimal_with_a_floor_of_one(monkeypatch):
def _seed_row(tmp_path, name, epoch, note="auto-registered by monitor", job=None):
SERVER = "t50-srv" # the server _create names; a monitor row carries the server it found the pane on
def _seed_row(tmp_path, name, epoch, note="auto-registered by monitor", job=None, server=SERVER):
row = {"name": name, "status": "running", "role": "creator", "herdr_session_created_at": "2026-10-11T00:00:00Z",
"herdr_session_epoch": epoch, "herdr_session": "default", "herdr_server": "default",
"herdr_session_epoch": epoch, "herdr_session": server, "herdr_server": server,
"pane": {"index": 0, "pid": 9999, "cmd": "claude", "cmd_full": "claude", "cwd": str(tmp_path)},
"last_visible_status": "running"}
if note:
@@ -151,7 +154,7 @@ def _seed_row(tmp_path, name, epoch, note="auto-registered by monitor", job=None
def _create(tmp_path, name, role="creator"):
return subprocess.run(["bash", str(tmp_path / "skills" / "multi-agent-mux-create" / "scripts" / "create_session.sh"),
"--workspace", str(tmp_path), "--agent", "claude", "--role", role, "--session", name,
"--no-onboard"], capture_output=True, text=True, cwd=str(tmp_path), timeout=300)
"--herdr-session", SERVER, "--no-onboard"], capture_output=True, text=True, cwd=str(tmp_path), timeout=300)
def _rows(yaml_path):
@@ -161,7 +164,7 @@ def _rows(yaml_path):
def test_a1_create_replaces_the_monitors_registration_of_its_own_pane(mam_sandbox, mock_herdr, mock_agents):
name = "t50-adopted-creator-claude"
yaml_path = _seed_row(mam_sandbox, name, int(time.time())) # registered after this create began
yaml_path = _seed_row(mam_sandbox, name, int(time.time()) + 60) # registered after this create began
res = _create(mam_sandbox, name, role="Creator") # the monitor guessed "creator" off the name
assert res.returncode == 0, res.stdout + res.stderr
assert f"NOTE: the monitor registered {name} while it was being created" in res.stdout
@@ -170,7 +173,7 @@ def test_a1_create_replaces_the_monitors_registration_of_its_own_pane(mam_sandbo
assert rows[0].get("last_visible_note") != "auto-registered by monitor" and rows[0].get("start_command")
@pytest.mark.parametrize("kind", ["real", "old-adoption", "adoption-with-job"])
@pytest.mark.parametrize("kind", ["real", "old-adoption", "adoption-with-job", "other-server"])
def test_a2_another_live_session_of_that_name_is_still_refused(mam_sandbox, mock_herdr, mock_agents, kind):
name = "t50-taken-creator-claude"
now = int(time.time())
@@ -178,6 +181,8 @@ def test_a2_another_live_session_of_that_name_is_still_refused(mam_sandbox, mock
yaml_path = _seed_row(mam_sandbox, name, now, note=None)
elif kind == "old-adoption":
yaml_path = _seed_row(mam_sandbox, name, now - 3600) # a pane from before this create
elif kind == "other-server": # review 1e25cf2f F2: names are unique per server
yaml_path = _seed_row(mam_sandbox, name, now + 60, server="different-server")
else:
yaml_path = _seed_row(mam_sandbox, name, now, job="deadbeef")
res = _create(mam_sandbox, name)