Skip to content

terminal: a herdr id may carry its socket, and one locator grammar for every kind (#1055) - #1168

Open
fujibee wants to merge 2 commits into
integration/terminal-driver-v1from
feat/1055-herdr-locator-grammar
Open

terminal: a herdr id may carry its socket, and one locator grammar for every kind (#1055)#1168
fujibee wants to merge 2 commits into
integration/terminal-driver-v1from
feat/1055-herdr-locator-grammar

Conversation

@fujibee

@fujibee fujibee commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Part of #1055 and #1152. Ref grammar only: no self-write, no sweep.

Why

A herdr pane id is unique inside one running herdr session and nowhere else. With two sessions running, both own a w1:p2, and every command that took a pane id reached whichever session the ambient environment named. On 2026-09-11 a repair resolved in one session landed in another session's live pane. The placement record (herdr:w1:p2) carried no session, so nothing could have known.

tmux has had the answer since #1051: the socket travels inside the id (<socket>:%4). This PR gives herdr the same shape, and gives every reader of a location one grammar.

What changes

A herdr id may carry its socket

<socket-path>:wN:pX is accepted everywhere a herdr id is; a bare wN:pX keeps meaning "the ambient instance", unchanged. A socket path may contain spaces. A colon or a control character in it is refused by name rather than mis-split: a colon in a POSIX path is legal, and a round-trippable encoding is deferred to #1166. The driver splits its own id (terminal_id_split), because the boundary inside an id is the driver's grammar: herdr ends in two colon fields, tmux and plain in one.

Every call about a pane goes through _herdr_cli <id> ..., which sets HERDR_SOCKET_PATH from a qualified id and hands the CLI the bare pane. Twenty-one calls in eleven functions were moved; terminal_arrange additionally refuses a source and target that name different sockets, before any call is made.

One locator grammar, one parser

<kind>:<instance>:<pane>herdr:/run/herdr-a.sock:w1:p7, tmux:/tmp/server with space:%4, plain:iterm:/dev/ttys040. agmsg_locator_compose <kind> <instance> <pane> and agmsg_locator_split <locator> live in terminal-registry.sh and consult the kind's driver for the boundary inside its id. Four seats read locators today; four parsers would disagree only after the fact. A refusal is one named reason on stderr and nothing on stdout: unknown_kind, instance_malformed (a colon in it, or none present where the id is otherwise valid), pane_malformed, id_malformed (the driver refused the whole id), locator_malformed.

The routing is checked by machine

A call that bypasses _herdr_cli raises no error: it goes to the ambient instance, on a pane id that may name a different seat's pane there. Twenty-one one-line edits are exactly the kind of change where one is missed in review, so .github/scripts/check-herdr-cli-routing.sh counts direct herdr <subcommand> calls per function by call position (the start of a statement, or right after $(, if, !, &&, ||, |, then, else, do; comment lines skipped; a herdr: inside a message never has a subcommand word after it) and compares each function with .github/herdr-cli-routing-allowlist, a <function> <count> table naming the six instance-wide functions that call the CLI directly. A function not listed fails; a listed count that went up fails; one that went down fails too and says to lower the entry, so a stale table cannot hide the next addition. An earlier count of these calls by plain grep answered 55 where the true number is 34, because comments and prose matched; the script does not repeat that. It runs from the bats suite (tests/test_herdr_cli_routing.bats), so the shards see it without a workflow change.

Tests

  • tests/test_locator.bats (11): compose and split for the three kinds including a socket path with spaces; every composed locator round-trips; refusals by name for a colon in the instance, an unknown kind, a pane outside the grammar, an empty instance, a bare pane, the plain - sentinel, control characters; the herdr split's dependence on the pane grammar having exactly one colon is pinned by a three-field pane that is refused, never split one field to the left; the loaded driver is not replaced by a split for another kind; the herdr id grammar and _herdr_cli (a qualified id reaches its socket and the CLI gets the bare pane; a bare id keeps the ambient socket; the id wins over the environment).
  • tests/test_herdr_cli_routing.bats (6): the driver sits at its allowlist; a new direct call in an unlisted function is red and named; an extra call inside a listed function is red (ABOVE); a vanished call is red (BELOW) and says to lower the entry; herdr: in a message and herdr in a comment are not calls; and end to end, seven pane-addressed ops on a qualified id all reach the id's socket with the bare pane and never the ambient one (argv and env logged by a fake herdr).
  • The existing terminal registry, peek/poke, spawn and self-name suites are unchanged and green on this branch; the three static checkers stay at their baselines; shellcheck reports nothing new on the four touched files.

Not in this PR

Producing qualified ids at the edges — terminal_spawn's returned id, terminal_detect, agmsg_terminal_self_env — stays bare (ambient) here; the sweep composes locators with agmsg_locator_compose, and the self-write path records the locator it is handed. When #1155 lands, its terminal_enumerate_panes calls herdr pane list under an explicit socket per instance; that function is instance-wide by design and belongs on the allowlist with its count — a one-line entry at merge time.

…mar for every kind (#1055)

A herdr pane id is unique inside one running session and nowhere else; with two
sessions both owning a w1:p2, every command that took a pane id reached
whichever session the ambient environment named, and a repair resolved in one
session landed in another's live pane. tmux has carried the socket inside the
id since #1051; herdr now does the same: <socket-path>:wN:pX is accepted
everywhere a herdr id is, a bare id keeps meaning the ambient instance, and a
colon or control character in the socket is refused by name (the round-trip
encoding is #1166). Every call about a pane goes through _herdr_cli <id>, which
sets HERDR_SOCKET_PATH from the id and hands the CLI the bare pane; arrange
refuses a source and target in different instances.

The registry gains the one locator grammar, <kind>:<instance>:<pane>, with
agmsg_locator_compose and agmsg_locator_split; each driver splits its own id
(terminal_id_split), because where an instance ends inside an id is the
driver's grammar. Refusals are one named reason on stderr.

A call that bypasses _herdr_cli is silent and lands on the wrong instance, so
.github/scripts/check-herdr-cli-routing.sh counts direct herdr calls per
function by call position and compares them with a named allowlist in both
directions; it runs from the bats suite as well.
…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.
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