Skip to content

fix(herdr): scope lab helper calls with a leading --session - #3

Merged
Aviator-Coding merged 4 commits into
mainfrom
fm/fm-herdr-lab-passthrough-leak
Aug 12, 2026
Merged

Aviator-Coding merged 4 commits into
mainfrom
fm/fm-herdr-lab-passthrough-leak

Conversation

@Aviator-Coding

Copy link
Copy Markdown
Owner

Intent

Close a confirmed fleet-safety hole in bin/fm-herdr-lab.sh, firstmate's own Herdr isolation helper.

THE DEFECT (reproduced, not theoretical). fm-herdr-lab.sh isolated a lab session by APPENDING a trailing flag in fm_herdr_lab_raw: HERDR_SESSION="$name" herdr "$@" --session "$name". The argument guard rejects a caller-supplied --session and any leading option, but NOT a bare '--'. Herdr documents agent-argument passthrough as herdr agent start <name> --kind K --pane ID -- <agent-args...>, so a legitimate call produced herdr agent start x --kind claude --pane w1:p8 -- --model haiku --session fm-lab-..., where everything after '--' is an AGENT argument. The isolating --session was swallowed. The client then fell back to the ambient environment, and HERDR_SOCKET_PATH BEATS HERDR_SESSION; in a real crewmate environment that socket points at the fleet's live 'default' session. So fm-herdr-lab.sh run <lab> ... -- <args> dispatched into the captain's live fleet - exactly the failure the helper exists to prevent. The contract's own line 'HERDR_SESSION alone is never accepted as isolation' is why it was not self-healing. Original reproduction: /Users/coder/firstmate/data/otel-herdr-surface/report.md section 1.3.

REQUIRED OUTCOME. Make the helper isolate correctly no matter what arguments a caller passes, and make it impossible for this class of bypass to return silently.

The reporting scout proposed three measures, to be evaluated ON THE MERITS rather than applied mechanically, implementing what genuinely closes the hole and stating why anything skipped was rejected:
(1) Reject a bare '--' in the argument guard. Cheap and explicit, but it REMOVES a legitimate Herdr capability, so consider whether that is acceptable or whether passthrough should still work.
(2) Prepend instead of append - herdr --session "$name" "$@". Herdr accepts a leading --session and no trailing '--' can displace it; preserves passthrough. Required: VERIFY EMPIRICALLY that a prepended --session is honoured for every subcommand form the helper supports, including agent start ... -- <args>; do not assume the top-level usage line generalises.
(3) Belt and braces: export HERDR_SOCKET_PATH to the lab socket inside fm_herdr_lab_raw so even total flag failure fails INTO the lab rather than into 'default'. Since socket path is what actually wins precedence, weigh this as arguably the most robust.

DECISIONS MADE AND WHY (deliberate; not oversights in the diff):

  • Measure 2 IMPLEMENTED. Verified empirically against the installed Herdr 0.8.0 that a leading --session is parsed before subcommand dispatch and is honoured for every form this helper issues (session list, server, status, session stop, session delete, workspace list, and agent start with '--' passthrough).
  • Measure 3 IMPLEMENTED IN INTENT, NOT LITERALLY: the code clears the ambient HERDR_SOCKET_PATH (env -u HERDR_SOCKET_PATH) instead of exporting a computed lab socket path. Exporting one would require deriving ~/.config/herdr/sessions//herdr.sock, a layout Herdr owns; a wrong derivation would be pinned ABOVE the correct flag and is a worse failure than none. Clearing removes the only variable that can point at 'default', and it was verified that with the socket cleared, HERDR_SESSION alone resolves to the lab - so a total flag failure still lands in the lab, which is measure 3's actual goal.
  • Measure 1 DELIBERATELY REJECTED: rejecting a bare '--' would remove agent start ... -- <agent-args>, which is precisely what a Herdr lab exists to exercise. With measures 2 and 3 in place the separator is harmless. The pre-existing blanket rejection of a caller-supplied --session anywhere (including after '--') was deliberately left as-is rather than relaxed.
  • The production adapter fm_backend_herdr_cli in bin/backends/herdr.sh has the same append shape and is a latent instance of the same class, but no caller passes a bare '--' and its arguments are all internally constructed; it was deliberately NOT changed, because clearing HERDR_SOCKET_PATH there could break a deliberate operator socket override. It is reported to firstmate as an adjacent finding instead of silently expanding this change.

REGRESSION COVERAGE (required: at minimum a test that a '--'-bearing command cannot reach the ambient session, colocated per repo convention). Because the verdict - which session a call is answered by - is vendor-emitted, .agents/skills/firstmate-coding-guidelines/SKILL.md requires two tests:

  • tests/fm-herdr-lab.test.sh: its fake Herdr client was rebuilt to MODEL Herdr's real resolution order (flag before subcommand wins; option parsing stops at a bare '--'; then HERDR_SOCKET_PATH; then HERDR_SESSION; then default) instead of asserting argument shape, which the guidelines forbid. Every helper call is now exercised under the realistic ambient condition (socket pointing at 'default'). Assertions are on the RESOLVED session. Each measure has its own independently failing case, verified by reverting each in turn.
  • tests/fm-herdr-lab-isolation-e2e.test.sh (new, family real-herdr-gated): guards that model against the installed client, including a control proving the client really drops a --session placed after '--', so the guard cannot pass vacuously. It contacts NO running server: it names a lab session and an ambient socket path that both do not exist, so the socket named in the client's own error is the evidence.

ALSO REQUIRED AND DONE: update the helper's header/usage text since the contract changed, and check whether the generated --herdr-lab brief text in bin/fm-brief.sh states anything now wrong - it did, in two places ('a trailing --session on every call' and 'The helper appends the required trailing --session'), both corrected, with tests/fm-brief.test.sh's assertion updated to match. docs/herdr-backend.md's lab-helper description corrected. A dated maintainer-verification record added to docs/verification/runtime-backends.md per the guidelines, pointing at the live guard as the refresh command.

SAFETY CONTEXT: this was fixed from inside the fleet it protects, where Herdr is the only worker runtime and 'default' hosts the captain's live fleet. All probes used an impossible pane ID and/or an unprovisioned lab name so a mis-routed call could only fail at resolution; no 'default' pane was touched; the default session was verified running and unchanged before and after, by socket path.

KNOWN PRE-EXISTING FAILURES, NOT INTRODUCED HERE (each confirmed to fail identically on the base commit with these changes stashed, and deliberately left alone rather than expanding scope): tests/fm-backend-herdr-presentation-e2e.test.sh workspace-ordering assertion, and tests/fm-kimi-harness.test.sh hook-install assertion. tests/fm-vendor-auth-probe.test.sh has a load-sensitive timing assertion that failed once under load and passes standalone.

What Changed

  • fm_herdr_lab_raw in bin/fm-herdr-lab.sh now issues env -u HERDR_SOCKET_PATH HERDR_SESSION="$name" herdr --session "$name" "$@" instead of appending a trailing --session. A leading flag is parsed before subcommand dispatch, so a caller's bare -- agent-argument separator can no longer swallow it, and clearing the ambient socket path - which outranks HERDR_SESSION and points at the spawning session inside any Herdr-managed pane - means even total flag failure resolves to the lab rather than to default. The bare -- is still accepted, so agent start ... -- <agent-args> keeps working; the guard against caller-supplied --session is unchanged, with its rejection message corrected to describe the leading flag.
  • Regression coverage: tests/fm-herdr-lab.test.sh's fake client was rebuilt to model Herdr's real resolution order (leading flag, then HERDR_SOCKET_PATH, then HERDR_SESSION, then default; option parsing stops at --) and now asserts on the resolved session under a realistic ambient socket, with an independently failing case per measure. New tests/fm-herdr-lab-isolation-e2e.test.sh (family real-herdr-gated, registered in bin/fm-test-run.sh) guards that model against the installed client, including a control proving a --session after -- really is dropped; it contacts no running server, naming a lab session and ambient socket that both do not exist.
  • Documentation caught up with the changed contract: the --herdr-lab brief text in bin/fm-brief.sh (two now-wrong statements about a trailing --session, with tests/fm-brief.test.sh updated to match), the helper header, docs/herdr-backend.md, a stale cross-reference comment in bin/backends/zellij.sh, and a dated Herdr 0.8.0 verification record in docs/verification/runtime-backends.md pointing at the live guard as its refresh command.

Verified with tests/fm-herdr-lab.test.sh (10/10), tests/fm-herdr-lab-isolation-e2e.test.sh (3/3 against herdr 0.8.0), tests/fm-herdr-session-cleanup-e2e.test.sh, tests/fm-brief.test.sh, and a before/after reproduction against real Herdr comparing the socket named in the client's own error. Mutation checks confirm each measure fails independently when reverted.

The same trailing-flag shape in fm_backend_herdr_cli (bin/backends/herdr.sh) was left alone: no caller passes a bare -- and its arguments are internally constructed, and clearing HERDR_SOCKET_PATH there could break a deliberate operator socket override. Rejecting a bare -- outright (the scout's first proposed measure) was also rejected, since it would remove the passthrough capability a Herdr lab exists to exercise.

Risk Assessment

✅ Low: The production change remains a single well-targeted line in fm_herdr_lab_raw plus a corrected error string, and the one substantive gap from round 1 - the primary leading --session measure having no independently failing regression case - is now closed by a toggle whose separation I verified by tracing both the current and reverted call shapes through the fake client's resolution order.

Testing

Ran the change's own regression tests (fake-client resolution model and the new real-Herdr isolation guard), the updated brief assertion, and the registry/docs meta-tests, all green; then went beyond pass/fail by reproducing the leak end-to-end against the installed Herdr 0.8.0 with the base helper and showing the fixed helper isolate under the identical ambient condition, mutation-checking each measure to prove its test fails independently, and exercising a real lab provision/run/teardown lifecycle plus a real-Herdr smoke test that route through the changed call site. No UI surface is involved - this is a shell helper, so the CLI transcript naming the socket that answered each call is the end-user-visible artifact. The live default session was confirmed running and unchanged by socket path around every real-Herdr run, no lab sessions or tripwires were left behind, and the only working-tree changes are two comment corrections.

Evidence: Before/after CLI transcript: passthrough isolation against real Herdr 0.8.0

$ herdr --version herdr 0.8.0 # Ambient crewmate identity for every probe below (Herdr exports this into # every pane it manages, pointing at the session that spawned the caller): # HERDR_SOCKET_PATH=/tmp/fm-evid-fleet-default.sock # HERDR_SESSION=default # Lab session under test: fm-lab-evid-70666 == BEFORE (base bb95bc6: trailing --session) == $ fm-herdr-lab.sh run fm-lab-evid-70666 agent start probe --kind claude --pane wZZ:p999999 -- --model haiku {"id":"cli:agent:start","error":{"code":"server_not_running","message":"no herdr server is running at /tmp/fm-evid-fleet-default.sock; run herdr to start or attach it"}} >>> LEAK: answered at the AMBIENT socket /tmp/fm-evid-fleet-default.sock - the captain's live fleet, not the lab. $ fm-herdr-lab.sh run fm-lab-evid-70666 agent start probe --kind claude --pane wZZ:p999999 (no passthrough separator) {"id":"cli:agent:start","error":{"code":"server_not_running","message":"no herdr server is running at /Users/coder/.config/herdr/sessions/fm-lab-evid-70666/herdr.sock; run herdr session attach fm-lab-evid-70666 to start or attach it"}} >>> Control: without a bare -- the old shape did isolate, which is why the hole stayed hidden. == AFTER (e89da54: leading --session + cleared socket) == $ fm-herdr-lab.sh run fm-lab-evid-70666 agent start probe --kind claude --pane wZZ:p999999 -- --model haiku {"id":"cli:agent:start","error":{"code":"server_not_running","message":"no herdr server is running at /Users/coder/.config/herdr/sessions/fm-lab-evid-70666/herdr.sock; run herdr session attach fm-lab-evid-70666 to start or attach it"}} >>> ISOLATED: answered at the LAB socket. Passthrough still works; the ambient socket is never touched. $ fm-herdr-lab.sh run fm-lab-evid-70666 workspace list {"id":"cli:workspace:list","error":{"code":"server_not_running","message":"no herdr server is running at /Users/coder/.config/herdr/sessions/fm-lab-evid-70666/herdr.sock; ..."}} $ fm-herdr-lab.sh run fm-lab-evid-70666 status --json {"client":{"version":"0.8.0","protocol":19,"session":"fm-lab-evid-70666"},"server":{"running":false,"socket":"/Users/coder/.config/herdr/sessions/fm-lab-evid-70666/herdr.sock","session":"fm-lab-evid-70666"}} >>> Every form the helper issues resolves to the lab session socket.

$ herdr --version
herdr 0.8.0

# Ambient crewmate identity for every probe below (Herdr exports this into
# every pane it manages, pointing at the session that spawned the caller):
#   HERDR_SOCKET_PATH=/tmp/fm-evid-fleet-default.sock
#   HERDR_SESSION=default
# Lab session under test: fm-lab-evid-70666

================ BEFORE (base bb95bc6: trailing --session) ================
$ fm-herdr-lab.sh run fm-lab-evid-70666 agent start probe --kind claude --pane wZZ:p999999 -- --model haiku
{"id":"cli:agent:start","error":{"code":"server_not_running","message":"no herdr server is running at /tmp/fm-evid-fleet-default.sock; run `herdr` to start or attach it"}}

>>> LEAK: answered at the AMBIENT socket /tmp/fm-evid-fleet-default.sock - the captain-s live fleet, not the lab.

$ fm-herdr-lab.sh run fm-lab-evid-70666 agent start probe --kind claude --pane wZZ:p999999   (no passthrough separator)
{"id":"cli:agent:start","error":{"code":"server_not_running","message":"no herdr server is running at /Users/coder/.config/herdr/sessions/fm-lab-evid-70666/herdr.sock; run `herdr session attach fm-lab-evid-70666` to start or attach it"}}

>>> Control: without a bare -- the old shape did isolate, which is why the hole stayed hidden.

================ AFTER (e89da54: leading --session + cleared socket) ================
$ fm-herdr-lab.sh run fm-lab-evid-70666 agent start probe --kind claude --pane wZZ:p999999 -- --model haiku
{"id":"cli:agent:start","error":{"code":"server_not_running","message":"no herdr server is running at /Users/coder/.config/herdr/sessions/fm-lab-evid-70666/herdr.sock; run `herdr session attach fm-lab-evid-70666` to start or attach it"}}

>>> ISOLATED: answered at the LAB socket. Passthrough still works; the ambient socket is never touched.

$ fm-herdr-lab.sh run fm-lab-evid-70666 agent start probe --kind claude --pane wZZ:p999999   (no passthrough separator)
{"id":"cli:agent:start","error":{"code":"server_not_running","message":"no herdr server is running at /Users/coder/.config/herdr/sessions/fm-lab-evid-70666/herdr.sock; run `herdr session attach fm-lab-evid-70666` to start or attach it"}}

$ fm-herdr-lab.sh run fm-lab-evid-70666 workspace list
{"id":"cli:workspace:list","error":{"code":"server_not_running","message":"no herdr server is running at /Users/coder/.config/herdr/sessions/fm-lab-evid-70666/herdr.sock; run `herdr session attach fm-lab-evid-70666` to start or attach it"}}

$ fm-herdr-lab.sh run fm-lab-evid-70666 status --json
{"client":{"version":"0.8.0","channel":"stable","protocol":19,"binary":"/opt/homebrew/bin/herdr","session":"fm-lab-evid-70666"},"server":{"status":"not_running","running":false,"version":null,"protocol":null,"capabilities":null,"compatible":null,"socket":"/Users/coder/.config/herdr/sessions/fm-lab-evid-70666/herdr.sock","session":"fm-lab-evid-70666","restart_needed":false},"update":{"restart_needed":false}}

>>> Every form the helper issues resolves to the lab session socket.
Evidence: Mutation check: each implemented measure has its own failing test

== Revert measure 2 (leading --session -> trailing --session) == 61: env -u HERDR_SOCKET_PATH HERDR_SESSION="$name" herdr "$@" --session "$name" ok - fm-herdr-lab: an agent-argument passthrough command cannot reach the ambient session not ok - a passthrough command without the session env fallback reached session 'default' instead of the lab: agent start probe --kind claude --pane w1:p8 -- --model haiku --session fm-lab-leading-flag-75593 == Revert measure 3 (drop env -u HERDR_SOCKET_PATH) == 61: HERDR_SESSION="$name" herdr --session "$name" "$@" not ok - a command whose session flag was ignored reached session 'default' instead of the lab: workspace list == Working tree restored == 61: env -u HERDR_SOCKET_PATH HERDR_SESSION="$name" herdr --session "$name" "$@"

# Mutation check: revert each measure in turn, confirm a test fails.

== Revert measure 2 (leading --session -> trailing --session) ==
61:  env -u HERDR_SOCKET_PATH HERDR_SESSION="$name" herdr "$@" --session "$name"
ok - fm-herdr-lab: an agent-argument passthrough command cannot reach the ambient session
not ok - a passthrough command without the session env fallback reached session 'default' instead of the lab: agent start probe --kind claude --pane w1:p8 -- --model haiku --session fm-lab-leading-flag-75593
exit=

== Revert measure 3 (drop env -u HERDR_SOCKET_PATH) ==
61:  HERDR_SESSION="$name" herdr --session "$name" "$@"
not ok - a command whose session flag was ignored reached session 'default' instead of the lab: workspace list
exit=

== Working tree restored ==
61:  env -u HERDR_SOCKET_PATH HERDR_SESSION="$name" herdr --session "$name" "$@"
Evidence: Live lab lifecycle against real Herdr, default fleet unchanged
=== default session BEFORE ===
{"default":true,"name":"default","running":true,"session_dir":"/Users/coder/.config/herdr","socket_path":"/Users/coder/.config/herdr/herdr.sock"}

$ bash tests/fm-herdr-session-cleanup-e2e.test.sh
ok - real named lab reproduced the exact restored one-tab one-pane childless no-agent shell shape
ok - real named lab cleanup closes only the exact stale pane and preserves exact focus
ok - real named lab cleanup is idempotent and leaves the default fleet session to the teardown tripwire
evidence: herdr=0.8.0 protocol=19 default-session-tripwire=armed

=== default session AFTER ===
{"default":true,"name":"default","running":true,"session_dir":"/Users/coder/.config/herdr","socket_path":"/Users/coder/.config/herdr/herdr.sock"}

$ bash tests/fm-herdr-lab-isolation-e2e.test.sh
ok - fm-herdr-lab: real Herdr answers a passthrough command from the lab session
ok - fm-herdr-lab: real Herdr answers a plain command from the lab session
ok - fm-herdr-lab: a session flag after the passthrough separator does not route on real Herdr
- Outcome: ⚠️ 1 info across 1 run (5m32s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 4 issues found → auto-fixed ✅
  • ⚠️ tests/fm-herdr-lab.test.sh:214 - The intent states "Each measure has its own independently failing case, verified by reverting each in turn", but measure 2 (the prepended --session) has no such case. Restoring the original append shape at bin/fm-herdr-lab.sh:61 (env -u HERDR_SOCKET_PATH HERDR_SESSION=&#34;$name&#34; herdr &#34;$@&#34; --session &#34;$name&#34;) leaves the entire suite green: in test_passthrough_cannot_reach_the_ambient_session the fake stops flag parsing at the bare -- so flag_session is empty, but env -u has already removed HERDR_SOCKET_PATH, so the fake falls through to HERDR_SESSION and still resolves to the lab. test_provision_run_and_guarded_teardown also still passes, because the fake strips --session from args before logging, so the stop/delete grep patterns match either ordering. Measure 3 masks measure 2, so the measure the intent calls primary is the only one with no regression guard - and it is the one that must hold if a future client drops HERDR_SESSION support. Suggested fix: add a fake toggle that ignores HERDR_SESSION (mirroring the existing FM_FAKE_HERDR_IGNORE_SESSION_FLAG at tests/fm-herdr-lab.test.sh:51) and assert a ---bearing call still reaches the lab under it.
  • ⚠️ bin/fm-herdr-lab.sh:144 - The guard's error message still says "run forbids caller-supplied --session; the helper appends the lab session". The helper no longer appends - line 61 now prepends the flag, and the header comment at lines 16-19 was updated to say so. This is the only text a caller actually sees when the guard fires, and it now describes the exact shape the change exists to remove, so anyone debugging a rejection is pointed at the wrong contract. Change "appends" to "prepends" (or "scopes the call with a leading --session") to match the header, bin/fm-brief.sh, and docs/herdr-backend.md, all of which were corrected in this change.
  • ℹ️ tests/fm-herdr-lab-isolation-e2e.test.sh:73 - The non-vacuity control deliberately issues a raw, unrouted herdr agent start from inside the fleet it is protecting: the isolating flag sits after the passthrough separator by design, so routing falls to HERDR_SOCKET_PATH=/tmp/fm-lab-amb-$$.sock. That path is never created, so on herdr 0.8.0 the call fails naming it and the assertion holds. But if a client ever resolved a missing socket by falling back to the live default server instead of erroring, this call would be dispatched into the captain's fleet, and the only remaining guard is the impossible pane id wZZ:p999999 causing a resolution-time rejection. The test also does no cleanup and records no tripwire, relying entirely on both the lab session and the ambient socket not existing. Noting the tradeoff, which the test comments already acknowledge, rather than proposing a change - the control is required for the guard not to be vacuous.
  • ℹ️ tests/fm-herdr-lab.test.sh:32 - The fake client's comment cites docs/verification/runtime-backends.md &#34;Herdr lab session routing&#34;, but the section added by this change is headed "Lab session routing" (docs/verification/runtime-backends.md:210 also refers to it by that name). The pointer that justifies the fake's resolution model does not match any heading in the file it names.

🔧 Fix: Give the leading --session measure its own failing test
✅ Re-checked - no issues remain.

⚠️ **Test** - 1 info
  • ℹ️ tests/fm-send-secondmate-marker-herdr-e2e.test.sh:15 - Applied two comment-only corrections in test files that still described the pre-fix helper contract, so the working tree is not clean: tests/fm-send-secondmate-marker-herdr-e2e.test.sh said the lab helper "appends its own required trailing --session before invoking real Herdr" (now "scopes the call with its own leading --session"), and tests/fm-backend-herdr-eventwait-smoke.test.sh said a routed call "carries the trailing --session" (now "is scoped to the lab session"). No assertions or behavior changed; both files still pass against real Herdr. Flagged because leaving the first statement in place is exactly the guidance that would lead a future maintainer to reintroduce this bug.
  • bash tests/fm-herdr-lab.test.sh - 10/10 pass, including test_passthrough_cannot_reach_the_ambient_session, test_leading_flag_alone_survives_the_passthrough_separator, and test_isolation_survives_losing_the_session_flag
  • bash tests/fm-herdr-lab-isolation-e2e.test.sh - 3/3 pass against the installed herdr 0.8.0, including the control proving the client really drops a --session placed after --
  • bash tests/fm-brief.test.sh - pass, covering the corrected --herdr-lab contract wording
  • Manual before/after reproduction against real Herdr 0.8.0: ran the base-commit helper (git show bb95bc6:bin/fm-herdr-lab.sh) and the target helper with identical args run &lt;lab&gt; agent start probe --kind claude --pane wZZ:p999999 -- --model haiku under HERDR_SOCKET_PATH/HERDR_SESSION=default, comparing the socket named in the client's own error
  • Mutation check: reverted measure 2 (leading --session -> trailing) and re-ran tests/fm-herdr-lab.test.sh - not ok - a passthrough command without the session env fallback reached session &#39;default&#39;
  • Mutation check: reverted measure 3 (dropped env -u HERDR_SOCKET_PATH) and re-ran tests/fm-herdr-lab.test.sh - not ok - a command whose session flag was ignored reached session &#39;default&#39;
  • bash tests/fm-herdr-session-cleanup-e2e.test.sh - live lab provision/run/teardown through the changed fm_herdr_lab_raw against real Herdr, 3/3 pass with the default-fleet tripwire armed and byte-identical
  • bash tests/fm-backend-herdr-eventwait-smoke.test.sh - 3/3 pass, exercising pane report-agent through fm_herdr_lab_cli against real Herdr
  • bash tests/fm-test-run.test.sh, bash tests/fm-test-isolation-proof.test.sh, bash tests/fm-documentation-audiences.test.sh - pass, covering the new test's family registration and the changed docs
  • bin/fm-test-run.sh --list --family real-herdr-gated plus a scan of every --list-lanes lane - confirms fm-herdr-lab-isolation-e2e.test.sh is real-herdr-gated only and never scheduled into a portable parallel lane
  • herdr session list --json before and after every real-Herdr run - the live default session stayed running with an unchanged socket path and no fm-lab- session was left behind
⚠️ **Document** - 1 info
  • ℹ️ bin/backends/herdr.sh:367 - Two code comments cite a docs/herdr-backend.md section titled "Session targeting: the --session flag, not HERDR_SESSION alone" (bin/backends/herdr.sh:367) and "Session targeting" (bin/fm-backend.sh:845), but no such heading exists; that material now lives under "## Current transport behavior". This is a pre-existing dangling pointer, unrelated to this change's contract, so I left it rather than widening the diff. Worth a one-line follow-up to repoint both comments.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

fm-herdr-lab.sh scoped every call by appending a trailing --session. Herdr
stops parsing options at a bare --, which its own documented agent-argument
passthrough requires, so `run <lab> agent start ... -- <agent args>` handed the
isolating flag to the agent instead of the client. The client then fell back to
the environment, where HERDR_SOCKET_PATH outranks HERDR_SESSION and, inside any
Herdr-managed pane, points at the session that spawned it - the live default
fleet session. The helper's whole purpose was defeated by a legitimate command.

Scope every call with a leading --session, which Herdr parses before subcommand
dispatch so no caller argument can displace it, and clear the ambient
HERDR_SOCKET_PATH so even a total flag failure resolves to the lab rather than
to the caller's own session. Agent-argument passthrough keeps working.

Verified against Herdr 0.8.0: a passthrough command through the fixed helper is
answered by the lab socket, the same command through the previous shape is
answered by the live default server, and every lifecycle form the helper issues
routes correctly with the flag leading.

Regression coverage sits on both sides of the vendor boundary, because the
verdict is the client's to give: tests/fm-herdr-lab.test.sh now models Herdr's
real resolution order rather than asserting argument shape, and pins each
measure with its own failing case; tests/fm-herdr-lab-isolation-e2e.test.sh
guards the model against the installed client and proves it is not vacuous.
That guard contacts no running server - it names a lab session and an ambient
socket that do not exist, so the socket in the client's own error is the proof.
@Aviator-Coding
Aviator-Coding merged commit 48ea5d1 into main Aug 12, 2026
13 checks passed
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