Skip to content

feat(herdr): unlock agent-state detection by naming the container client after the agent - #28

Merged
TakiTake merged 2 commits into
mainfrom
feat/herdr-agent-arg0
Jul 12, 2026
Merged

feat(herdr): unlock agent-state detection by naming the container client after the agent#28
TakiTake merged 2 commits into
mainfrom
feat/herdr-agent-arg0

Conversation

@TakiTake

Copy link
Copy Markdown
Owner

Summary

Follow-up to #23, fixing the gap it documented: herdr never tracked agent state (idle/working/blocked) for a pall8t-sandboxed pane. Confirmed from herdr's source (ogulcancelik/herdr): pane identity comes exclusively from the host process tree — on macOS the argv0 basename via sysctl(KERN_PROCARGS2) (platform/macos.rs::process_argv0_name, preferred by detect::normalized_process_name) — and that identity is what gates herdr's screen-content state detection. pall8t exec-replaces itself with container, which herdr doesn't recognize, so detection never ran; the pane's PTY shows the agent's real TUI, so identity was the only missing piece. herdr's own HERDR_AGENT env hint is Linux-only (/proc/<pid>/environ; the macOS process_agent_hint is a stub), and its Claude integration hooks need HERDR_* env plus the host socket, neither reachable inside the VM.

The fix: pall8t run execs the container client with argv[0] set to the agent's name (CommandExt::arg0 — binary resolution still finds container on PATH). Guardrails, shaped by four review rounds:

  • Derivation is allowlist-only. The first run-command token whose name herdr recognizes (KNOWN_AGENTS, mirroring herdr's identify_agent aliases) is used; launchers (env FOO=1 claude, npx -y codex, uv run claude) and pkg@version/@scope/pkg specs are looked through. Only the first token may be a path, so argument paths and VAR=VAL assignments (env HOME=/home/claude codex) can never mislabel the pane. Anything unrecognized yields no hint — never a guess.
  • HERDR_AGENT is the fallback, honored by pall8t on macOS for commands with no recognizable name (wrapper scripts, sh -c …). A name found in the command wins over it — an env var baked into a shell function is ambient — and pall8t prints a note when it ignores one. The README's p8() example no longer hardcodes it.
  • No hint → no side effects at all: argv0 stays container and no display report is sent (status quo of herdr integration (Phase 1) + pall8t ls status-parsing fix #23). With a hint, the sidebar report becomes --display-agent "<agent> (pall8t)" instead of hardcoded claude. Verified against herdr's metadata guards that the report (sent without --agent) is unaffected by the argv0 change.
  • pall8t exec never hints — an ambient HERDR_AGENT would mislabel a one-off debug shell.
  • pall8t herdr doctor now reports HERDR_AGENT (informational).

Known accepted residuals: a bare agent-named argument before the real program (e.g. tmux … -s claude codex passed explicitly) can still match — it has no syntactic tell; KNOWN_AGENTS may drift behind herdr — drift is safe (no hint, HERDR_AGENT covers it). Upstream, implementing herdr's process_agent_hint on macOS (the KERN_PROCARGS2 buffer already contains the environment) would make HERDR_AGENT work natively; worth filing.

Test plan

  • cargo test (131 tests, incl. new derivation/precedence/no-guess tables), cargo clippy --all-targets -- -D warnings on both aarch64-unknown-linux-gnu and aarch64-apple-darwin, cargo fmt --check
  • 4 rounds of adversarial + multi-agent review; final round clean
  • Live (host, must be done before merging): herdr agent start … -- pall8t run in this repo — confirm (1) the run still works end to end with argv0 spoofed, i.e. Apple's container CLI doesn't misbehave when its argv[0] is claude (Swift ArgumentParser uses it for usage text only, but this is the one assumption not verifiable from inside the sandbox); (2) the herdr sidebar shows working/idle/blocked transitions for the sandboxed claude; (3) ps shows the client process as claude (cosmetic, expected)

🤖 Generated with Claude Code

TakiTake and others added 2 commits July 12, 2026 04:00
…ent after the agent

herdr assigns pane identity from the host process tree only — on macOS
the argv0 basename via sysctl(KERN_PROCARGS2) — and that identity gates
its screen-content state detection (idle/working/blocked). pall8t
exec-replaces itself with `container`, which herdr doesn't recognize, so
a sandboxed pane's state was never tracked (the gap README documented
after #23). The pane's terminal shows the agent's real UI, so detection
works the moment the pane has the right identity.

`pall8t run` now execs the container client with argv[0] set to the
sandboxed agent's name: the first token of the run command that herdr
recognizes (KNOWN_AGENTS allowlist mirroring herdr's identify_agent;
launchers like env/npx/uv and pkg@version specs are looked through, and
only the first token may be a path so argument paths and VAR=VAL
assignments can never mislabel the pane), falling back to HERDR_AGENT
when the command contains no recognized name — herdr's own env hint is
Linux-only (/proc environ), so pall8t honors it on macOS. Unrecognized
names are never asserted: no hint means no argv0 override and no display
report, and a note is printed when a derived name shadows a differing
HERDR_AGENT. The sidebar display report now interpolates the derived
agent, `pall8t herdr doctor` reports HERDR_AGENT, and `pall8t exec`
never hints (an ambient HERDR_AGENT would mislabel a debug shell).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rgv[0]

Live testing found the argv0 agent hint never reached the long-lived
client process: Homebrew installs `container` as a bash wrapper
(`CONTAINER_INSTALL_ROOT="…" exec "<cellar>/libexec/container" "$@"`)
whose inner exec rewrites argv[0] to the full Cellar path — confirmed
via ps, and confirmed as the only broken link by rebuilding herdr 0.7.3
from source and replaying the captured claude byte stream through it
(idle→working→idle transitions work once the pane is identified; the
titles also verifiably reach the host PTY).

When an agent hint exists, pall8t now resolves `container` on PATH and,
if it is a strict pass-through exec wrapper, execs the wrapper's target
binary directly with the wrapper's env assignments applied, so argv[0]
survives. Anything unparseable (real binary, symlink, wrapper with
extra logic) falls back to the previous behavior. The client process
also gets HERDR_AGENT set, so a future herdr macOS env hint could carry
the name even where argv0 cannot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@TakiTake

Copy link
Copy Markdown
Owner Author

Live testing revealed the argv0 hint was being destroyed one hop after pall8t set it: Homebrew installs container as a bash exec wrapper, and its inner exec "<cellar>/libexec/container" "$@" rewrites argv[0] to the full Cellar path. herdr therefore never identified the pane (API showed agent: null, status: unknown).

Everything else was verified working along the way:

  • sandboxed claude emits the Braille-spinner OSC titles herdr's osc_title_working rule needs, even with the container's bare TERM=xterm (captured in-container)
  • the titles arrive on the host PTY intact (captured on the host through the container client)
  • herdr 0.7.3 built from source + its bundled claude manifest (byte-identical to the herdr.dev remote) turns that exact byte stream into idle → working → idle (reproduced end-to-end with a replay harness)

New commit: when an agent hint exists, pall8t resolves container on PATH and, if it's a strict pass-through exec wrapper, execs the wrapper's target directly with the wrapper's env assignments (CONTAINER_INSTALL_ROOT) applied so argv[0] survives; anything unparseable falls back to the old behavior. HERDR_AGENT is also set on the client process for a potential future herdr macOS env hint.

Remaining live verification (first true test of the Cellar binary running under a foreign argv[0] — the wrapper had shielded it until now): rebuild from this branch and run under herdr; confirm (1) the container still launches, (2) ps shows claude run -i -t …, (3) the pane turns working during a task.

🤖 Generated with Claude Code

@TakiTake

Copy link
Copy Markdown
Owner Author

Live verification complete on a Homebrew apple/container install: the Cellar binary launches normally under the foreign argv[0], ps shows claude run -i -t …, and herdr identifies the pane and tracks working/idle state for the sandboxed claude. All three test-plan items pass — ready to merge.

🤖 Generated with Claude Code

@TakiTake
TakiTake merged commit e2af988 into main Jul 12, 2026
2 checks passed
@TakiTake
TakiTake deleted the feat/herdr-agent-arg0 branch July 12, 2026 10:56
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