fix(issue-50): an injected record holds its session whatever its FIFO place; the ambient pane claim locks the caller's server (reviews 5a09a13f F-A, e8e471ff R1)

open_jobs reports a live pending record nobody signals on as `injected`:
its producer registered and injected it without a waiter (create
--onboard, a job sent by hand), so it holds the session like a running or
admitted job, and a waiter of the same second with a lower job id, or one
registered a moment earlier and still in its preflight, no longer passes
it in the FIFO. The waiter signals on its own record before its first
scan, so its record is never mistaken for one.

lib_py.pane_lock resolves the shim's ambient target (no -L,
HERDR_SESSION_NAME empty or `default`) to the server of the pane the
caller runs in - HERDR_SESSION, else HERDR_SOCKET_PATH's layout, as
lib.sh _herdr_caller_server reads them - so an ambient and a named call
into one server claim under one lock; outside a pane it stays the
default server's.

Tests: u2h (the tie, the later-registered onboarding, the signalled
control), u1/u4 labels, c10 forces the same-second registration, p1b,
s2 (real herdr: held lock stops both call forms; mixed concurrent calls
take distinct panes). Two FIFO fixtures now carry a waiter's signal.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
GodopuandClaude Fable 5.1 committed 2026-10-11 08:50:46 +00:00
1 parent d50610c1ab
commit 6c99fedf40
7 files changed
+168 -22

No files matched your search

+25 -2
View File
@@ -8,7 +8,13 @@ and the later `agent start` failed with agent_pane_busy. The shim now holds
one lock per server from the choice to the claim.
pane_lock.py path --session S the lock file of herdr session S, under the
HOME whose socket the session uses
HOME whose socket the session uses. S empty
(or `default`) is the shim's ambient target:
herdr then answers on the server of the pane
this runs in (HERDR_SESSION / HERDR_SOCKET_PATH,
as lib.sh _herdr_caller_server reads them), or
the default server outside any pane - the
lock is that server's (review e8e471ff R1)
pane_lock.py acquire --fd N flock(LOCK_EX) on the inherited fd N; prints
`locked`, or `timeout` once MAM_PANE_CLAIM_WAIT_S
(default 120, minimum 1) has passed
@@ -32,9 +38,26 @@ from lib_py import ints
DEFAULT_WAIT_S = 120
def caller_server(env: Mapping[str, str]) -> str:
"""The server whose pane this process runs in, by the pane's exports:
HERDR_SESSION when present, else HERDR_SOCKET_PATH's layout
($HOME/.config/herdr/herdr.sock -> default, .../sessions/<name>/herdr.sock
-> name); "" outside a pane or for an unrecognised layout."""
if env.get("HERDR_SESSION"):
return env["HERDR_SESSION"]
sock = env.get("HERDR_SOCKET_PATH") or ""
if sock.endswith("/.config/herdr/herdr.sock"):
return "default"
if sock.endswith("/herdr.sock") and "/.config/herdr/sessions/" in sock:
return sock[:-len("/herdr.sock")].rsplit("/", 1)[-1]
return ""
def lock_path(session: Optional[str], env: Mapping[str, str]) -> str:
home = env.get("HOME") or env.get("HOME_DIR") or os.path.expanduser("~")
return os.path.join(home, ".local", "state", "mam", "pane-lock", (session or "default") + ".lock")
if session in (None, "", "default"):
session = caller_server(env) or "default"
return os.path.join(home, ".local", "state", "mam", "pane-lock", session + ".lock")
def wait_s(env: Mapping[str, str]) -> int: