Skip to content

feat(terminal): enumerate panes across every instance, as (kind, instance, pane) (#1152) - #1155

Merged
fujibee merged 5 commits into
integration/terminal-driver-v1from
feat/1152-sweep-enumeration
Sep 12, 2026
Merged

feat(terminal): enumerate panes across every instance, as (kind, instance, pane) (#1152)#1155
fujibee merged 5 commits into
integration/terminal-driver-v1from
feat/1152-sweep-enumeration

Conversation

@fujibee

@fujibee fujibee commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Part of #1152 — priority 1 of the sweep: enumerate panes across every instance,
as (kind, instance, pane) triples. Independent of #1154; base is
integration/terminal-driver-v1.

A bare pane id is not an address

Measured on one machine, 2026-09-11, with two herdr instances running:

instance   response pane_id   shell_pid   who is actually there
jugemu     w1:p7              2727        one team's seat
oma        w1:p7              80649       a different team's seat

Enumerating both produced 52 rows in which w1:p1, w1:p2, w1:p4, w1:p5 and
w1:p7 each 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 its
rule 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

<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.

Three things the measurements changed

  • A socket proves nothing; it outlives its server. Only no server running /
    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.
    A socket that is merely bound with nothing listening does report no server running
    and is correctly skipped. My first control was the second kind, so it exercised the
    stale path while claiming to test the unreadable one.
  • 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 and id -un returned the bare uid.
  • The glob runs in a subshell with failglob dropped. 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 arm inside that substitution needs its leading (: bash 3.2 counts parens when
    parsing $( ), so a pattern's ) ends the substitution early.

Controls

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.

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 the
op'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 last
candidate — so a version with no restore at all left plain loaded and the test passed
on iteration order (measured: zero reds). And three negations were written
! cmd | grep -q … in non-final position, where bash cannot fail the test (#670); the
enforced-assertions checker named all three, run synchronously before asking for review.

…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
fujibee force-pushed the feat/1152-sweep-enumeration branch from f2ba1ca to 0ed6de3 Compare September 12, 2026 01:30
@fujibee
fujibee merged commit 2c6af07 into integration/terminal-driver-v1 Sep 12, 2026
27 checks passed
@fujibee
fujibee deleted the feat/1152-sweep-enumeration branch September 12, 2026 04:18
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant