From d115bf71560a527354dc042eb9e7848f787c8637 Mon Sep 17 00:00:00 2001 From: Godopu Date: Wed, 21 Oct 2026 19:48:44 +0000 Subject: [PATCH] fix(id_order): reserve startup when resume resolves an unrecorded uuid (Issue #63) --- .agents/skills/lib_py/agents/id_order.py | 29 ++++++------ tests/test_issue63_same_cwd_ids.py | 56 ++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 13 deletions(-) diff --git a/.agents/skills/lib_py/agents/id_order.py b/.agents/skills/lib_py/agents/id_order.py index 97f5074..d31fc92 100644 --- a/.agents/skills/lib_py/agents/id_order.py +++ b/.agents/skills/lib_py/agents/id_order.py @@ -122,12 +122,14 @@ def blockers(state: Dict[str, Any], cwd: str, agent: str, me: str, prev_epoch = int(m.get("prev_epoch") or 0) prev_end_epoch = int(m.get("prev_end_epoch") or 0) + pinned_for_attempt = bool(cur_own) and ( + cur_own != prev_own + or (status == "running" and row_epoch > prev_epoch) + ) + if pinned_for_attempt: + continue if status == "running": - pinned_for_attempt = bool(cur_own) and ( - cur_own != prev_own or row_epoch > prev_epoch - ) - if not pinned_for_attempt: - found.append(m_sess) + found.append(m_sess) else: ended_after_attempt = ( end_epoch > max(m_at, prev_end_epoch) @@ -231,14 +233,15 @@ def wait(cwd: str, agent: str, session: str, owner_pid: int, markers = _read_and_prune_markers(dpath, session, is_alive=is_alive) blks = blockers(state, cwd, agent, session, markers, is_alive=is_alive) if not blks: - if not uuid and owner_pid and owner_pid > 0: - prev_row = next( - ( - s for s in (state.get("herdr_sessions") or []) - if isinstance(s, dict) and s.get("name") == session - ), - None, - ) + prev_row = next( + ( + s for s in (state.get("herdr_sessions") or []) + if isinstance(s, dict) and s.get("name") == session + ), + None, + ) + already_recorded = bool(uuid) and own_id(prev_row, agent) == uuid + if not already_recorded and owner_pid and owner_pid > 0: write_marker(dpath, session, owner_pid, cwd, agent, prev_row=prev_row) return 0 diff --git a/tests/test_issue63_same_cwd_ids.py b/tests/test_issue63_same_cwd_ids.py index d7fdd42..0e95446 100644 --- a/tests/test_issue63_same_cwd_ids.py +++ b/tests/test_issue63_same_cwd_ids.py @@ -978,3 +978,59 @@ def test_canonical_cwd_distinguishes_underscore_hyphen_and_nested_paths_with_sym res_hyphen = decide_resume(state_stopped, "hyphen-agy", "agy", str(ws_hyphen), home=str(home)) assert res_hyphen["uuid"] == UB assert res_hyphen["notes"] == [] + + +def test_id_order_resolved_but_unrecorded_resume_uuid_reserves_startup_until_recorded(tmp_path, monkeypatch): + """When a single stopped post-discovered row with no own_id resolves a uuid from disk, + id_order.wait reserves startup (writes a marker) until that uuid is recorded on the row.""" + from lib_py.agents import id_order + from lib_py.agents.session_ids import decide_resume + + home = tmp_path / "home" + ws = tmp_path / "ws" + ws.mkdir(parents=True) + yaml_path = tmp_path / "agent-sessions.yaml" + monkeypatch.setenv("HOME", str(home)) + monkeypatch.setenv("AGENT_SESSIONS_YAML", str(yaml_path)) + + _agy_conv(home, UA, time.time() - 10) + _agy_cache(home, {ws: UA}) + + old_epoch = int(time.time()) - 100 + row_a = _row("same-ws-creator-agy", "agy", ws, old_epoch, status="stopped", agy_conversation_id_own=None) + state = {"herdr_sessions": [row_a]} + yaml_path.write_text(yaml.safe_dump(state)) + + dec = decide_resume(state, "same-ws-creator-agy", "agy", str(ws), home=str(home)) + assert dec["uuid"] == UA + + proc_a = subprocess.Popen(["sleep", "120"]) + proc_b = subprocess.Popen(["sleep", "120"]) + try: + # Admit A with resolved (but not yet recorded) uuid UA -> writes startup marker + assert id_order.wait( + str(ws), "agy", "same-ws-creator-agy", proc_a.pid, uuid=UA, + yaml_path=str(yaml_path), home=str(home), wait_s_val=0, + ) == 0 + + # While A has not yet recorded UA on its row, B must be refused at cap 0 + assert id_order.wait( + str(ws), "agy", "same-ws-reviewer-agy", proc_b.pid, uuid="", + yaml_path=str(yaml_path), home=str(home), wait_s_val=0, + ) == 1 + + # Once A records UA on its row (via update_yaml_resumed.sh), B is released + recorded_a = _row( + "same-ws-creator-agy", "agy", ws, old_epoch + 50, + pid=proc_a.pid, status="running", agy_conversation_id_own=UA, + ) + yaml_path.write_text(yaml.safe_dump({"herdr_sessions": [recorded_a]})) + assert id_order.wait( + str(ws), "agy", "same-ws-reviewer-agy", proc_b.pid, uuid="", + yaml_path=str(yaml_path), home=str(home), wait_s_val=0, + ) == 0 + finally: + proc_a.kill() + proc_b.kill() + proc_a.wait() + proc_b.wait()