feat(terminal): enumerate panes across every instance, as (kind, instance, pane) (#1152) - #1155
Merged
fujibee merged 5 commits intoSep 12, 2026
Merged
Conversation
fujibee
force-pushed
the
feat/1152-sweep-enumeration
branch
from
September 11, 2026 17:43
30acfac to
cf4843b
Compare
fujibee
force-pushed
the
feat/1152-sweep-enumeration
branch
from
September 11, 2026 22:29
cf4843b to
36ccff2
Compare
…ance, pane) A bare pane id is not an address. Measured on one machine, 2026-09-11: two herdr instances were running, and enumerating both produced 52 rows in which w1:p1, w1:p2, w1:p4, w1:p5 and w1:p7 each appeared TWICE -- the same id naming a different team's seat in each. Two throwaway tmux servers showed the same with %0. So nothing here emits a pane id on its own; the instance travels with it, and the instance is a socket path -- the value that makes the row answerable again -- not a display name. The tmux half is RESURRECTED from PR #1147 (branch fix/1146-tmux-search-all-servers) rather than rewritten. Its socket enumeration and, more importantly, its rule for when a server counts as dead were already written and measured there; that PR was closed only because a different design was expected to replace it, and that expectation is gone. One verdict changed in the move. #1146's search asked "is this label unique across every server?", so a server it could not read POISONED the whole answer. This op asks "what is out there?", and for that question one unreadable server must not lose the panes of the servers that answered. So the hole is named and enumeration continues. Same mechanics, different verdict, because the question is different. Four causes, four row kinds, none folded: <kind> <instance> <pane> a pane was observed there ! <kind> <instance> that instance could not be read !! <kind> that terminal's instance LIST could not be read ? <kind> that terminal cannot enumerate at all `?` and `!!` look identical from outside -- no rows for that kind -- and mean opposite things to a caller: stop asking, versus retry. `?` is also not `!`. Three things the measurements changed: - A socket proves nothing; it outlives its server. Only tmux saying `no server running` or `no such file or directory` is evidence of death. Measured: a live NON-tmux listener answers `server exited unexpectedly`, which is not that, so it becomes a named hole rather than a silent drop. - An `id` that cannot answer must not become "this user has no servers" -- the socket directory could not be NAMED. Observed on this machine while directory services were degraded. - The socket glob runs in a subshell with failglob dropped and nullglob set. Measured, both interpreters: with a caller's `shopt -s failglob` and an empty socket directory, a bare `for sock in "$dir"/*` produced NO OUTPUT AT ALL -- the shell died at the expansion, before any row could be printed. nullglob does not cover it; failglob wins. And the `case` pattern inside that substitution needs its leading `(`: bash 3.2 counts parens when parsing `$( )`, so a case arm's `)` ends the substitution early. 15 tests, driving fake tmux and herdr binaries so the error texts and exit statuses are the test's property rather than the runner's. The same four outcomes were also produced against real tmux servers in one observation, and agreed.
Both were green for reasons unrelated to what they claimed. The "driver is restored" test loaded `plain` before enumerating. `plain` is the LAST candidate, so a version with no restore at all still left it loaded and the test passed on the iteration order. Measured: deleting the restore reddened nothing. It now loads the FIRST candidate, and pins the ordering assumption it depends on, so the test weakens loudly rather than quietly if that changes. Three negations were written `! cmd | grep -q ...` in non-final position, where bash cannot fail the test (#670) -- the enforced-assertions checker names all three. They use the `refute` helper instead. Checker back at its baseline.
…xcludes it The #1051 test DERIVES the ops it sweeps -- the ABI minus the ops that take no pane id -- so a new op joins the sweep automatically. That is the right default and it is why this showed up at all: adding terminal_enumerate_panes took the count from 10 to 11 while the op invoked tmux zero times in the fixture, and the positive control caught the mismatch instead of the sweep shrinking in silence. It belongs on the exclusion list for the same reason terminal_find_by_label does: there is no owning server in its INPUT to honour, because it takes no input. It is the op that discovers servers. It still honours #1051 -- each server is addressed with its own socket and every row leaves socket-qualified -- and that is checked in test_sweep_enumeration.bats, where a fake tmux answers per socket. Reverting the qualification there reddens five tests.
Both from review, both measured before and after. A socket path was read with `for x in $(sqlite3 …)`, which word-splits. A home directory with a space in it is ordinary, and the splitting turned ONE instance into THREE fabricated ones -- each reported as holding the same pane, so the damage reads as MORE coverage rather than less. Reproduced: `/a path/with spaces/herdr.sock` became `/a`, `path/with`, `spaces/herdr.sock`. It is now read one line per path, and a path carrying a control byte is refused with a named hole rather than carried into a line-based channel. The restore-the-caller's-driver step only restored a NAMED driver, so a caller that deliberately held NO driver was handed the last candidate by a function it called to read. "None" is a state: the ops are unset, which is the same teardown agmsg_terminal_load does before it loads. Two controls, each red without its fix and green with it.
fujibee
force-pushed
the
feat/1152-sweep-enumeration
branch
from
September 12, 2026 01:30
f2ba1ca to
0ed6de3
Compare
fujibee
added a commit
that referenced
this pull request
Sep 12, 2026
…grammar Resolutions: herdr peek and poke keep #1164's forwarded diagnostics with the socket routing re-applied; the registry keeps both the locator section and the enumeration section (the merge had dropped the split's closing brace). Review findings folded in: terminal_pane_process_observe takes a pane id, so it routes through _herdr_cli with the bare pane and leaves the allowlist, with a control that two instances sharing a bare pane answer only from the id's socket; every comparison of a response pane_id against the caller's id now compares the bare pane. The routing checker also counts a call behind an assignment prefix (HERDR_SOCKET_PATH=... herdr ...), the exact form a bypass takes; terminal_enumerate_panes is listed at two, instance-wide by design.
fujibee
added a commit
that referenced
this pull request
Sep 12, 2026
…te path Both sides appended to the same three driver files and to the optional-ops list. Resolutions keep both: terminal_fence beside the new pane-process observation and enumeration ops, and the optional list carries every name. The merge had dropped the fence's closing `return 0` and brace in the herdr and tmux drivers; both restored, and every file parses.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #1152 — priority 1 of the sweep: enumerate panes across every instance,
as
(kind, instance, pane)triples. Independent of #1154; base isintegration/terminal-driver-v1.A bare pane id is not an address
Measured on one machine, 2026-09-11, with two herdr instances running:
Enumerating both produced 52 rows in which
w1:p1,w1:p2,w1:p4,w1:p5andw1:p7each appeared twice. Two throwaway tmux servers showed the same with%0.So nothing here emits a pane id on its own, and the instance that travels with it is a
socket path — the value that makes the row answerable again — not a display name.
This also means an id-echo canary cannot tell a wrong-instance answer from a right one:
both instances answer
pane_id=w1:p7. That hole is reported separately against #1154.The tmux half is resurrected, not rewritten
From PR #1147 (branch
fix/1146-tmux-search-all-servers). Its socket enumeration and itsrule for when a server counts as dead were already written and measured there; the PR was
closed only because a different design was expected to replace it.
One verdict changed in the move. #1146's search asked "is this label unique across
every server?", so a server it could not read poisoned the whole answer. This op asks
"what is out there?", and for that question one unreadable server must not lose the
panes of the servers that answered. The hole is named and enumeration continues — same
mechanics, different verdict, because the question is different.
Four causes, four row kinds, none folded
?and!!look identical from outside — no rows for that kind — and mean oppositethings to a caller: stop asking, versus retry.
Three things the measurements changed
no server running/no such file or directoryis evidence of death. Measured: a live non-tmux listeneranswers
server exited unexpectedly, which is not that — so it becomes a named hole.A socket that is merely bound with nothing listening does report
no server runningand is correctly skipped. My first control was the second kind, so it exercised the
stale path while claiming to test the unreadable one.
idthat cannot answer must not become "this user has no servers" — the socketdirectory could not be named. Observed on this machine while directory services were
degraded and
id -unreturned the bare uid.failglobdropped. Measured, both interpreters:with a caller's
shopt -s failgloband an empty socket directory, a barefor sock in "$dir"/*produced no output at all — the shell died at the expansion,before any row could be printed.
nullglobdoes not cover it;failglobwins. And thecasearm inside that substitution needs its leading(: bash 3.2 counts parens whenparsing
$( ), so a pattern's)ends the substitution early.Controls
15 tests, driving fake
tmuxandherdrbinaries so the error texts and exit statusesare the test's property rather than the runner's. The same four outcomes were also
produced against real tmux servers in one observation and agreed.
14 mutations, each with its own red: dropping the socket qualification → 5; dropping a
named hole → 1 each; widening the dead-pattern → 1; softening the uid guard → 1; removing
the failglob subshell → 1; the herdr session/pane strictness → 1 each; visiting
non-running instances → 1; dropping the kind → 2; folding
?into!!→ 1; piping theop's status instead of capturing it → 2; dropping the driver restore → 1. Clean: 0.
Two of those controls could not fail when first written, and the numbers above are
after fixing them. The "driver is restored" test loaded
plain, which is the lastcandidate — so a version with no restore at all left
plainloaded and the test passedon iteration order (measured: zero reds). And three negations were written
! cmd | grep -q …in non-final position, where bash cannot fail the test (#670); theenforced-assertions checker named all three, run synchronously before asking for review.