fix(id_order): a dead-owner marker on the caller's own session follows the orphan rules (Issue #63)

A marker left on the invoking session name by a killed create/resume was
pruned before herdr was asked, so a retry under the same name took it over
while the orphaned pane was still alive (and create's failure cleanup could
then kill it). It is now kept and blocks like any other orphan until its
row is registered for the attempt or herdr reports the session gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
GodopuandClaude Opus 5.5 committed 2026-10-23 11:47:04 +09:00
1 parent 7cac6f613e
commit 1614af5b67
2 files changed
+42 -16

No files matched your search

+7 -6
View File
@@ -180,15 +180,16 @@ def _registered_for_attempt(m: Dict[str, Any], row: Optional[Dict[str, Any]]) ->
return int(row.get("herdr_session_epoch") or 0) > int(m.get("prev_epoch") or 0)
def _read_and_prune_markers(dpath: str, me: str, my_pid: int, state: Dict[str, Any],
def _read_and_prune_markers(dpath: str, my_pid: int, state: Dict[str, Any],
is_alive: Callable[[int], bool] = pid_alive,
pane_state: Callable[[str, str], Optional[bool]] = (
lambda server, session: None),
) -> List[Dict[str, Any]]:
"""Markers that still count. A live owner keeps its marker (another live
attempt on `me` included). A dead owner's marker goes once its row is
registered for the attempt (the row decides from then on) or herdr answers
that its session is gone; until then it is kept as an orphan."""
attempt on the caller's session included). A dead owner's marker goes once
its row is registered for the attempt (the row decides from then on) or
herdr answers that its session is gone; until then it is kept as an orphan,
one an earlier attempt left on the caller's session included."""
rows_by_name = {
str(s.get("name") or ""): s
for s in (state.get("herdr_sessions") or []) if isinstance(s, dict)
@@ -219,7 +220,7 @@ def _read_and_prune_markers(dpath: str, me: str, my_pid: int, state: Dict[str, A
drop = True
elif is_alive(owner_pid):
drop = False
elif sess == me or not sess:
elif not sess:
drop = True
elif _registered_for_attempt(data, rows_by_name.get(sess)):
drop = True
@@ -304,7 +305,7 @@ def wait(cwd: str, agent: str, session: str, owner_pid: int,
file=sys.stderr, flush=True,
)
return 1
markers = _read_and_prune_markers(dpath, session, owner_pid, state,
markers = _read_and_prune_markers(dpath, owner_pid, state,
is_alive=is_alive, pane_state=bounded_probe)
blks = blockers(state, cwd, agent, session, markers, is_alive=is_alive)
if not blks: