herdr skills: document the two cross-pane delivery mechanics - #17
Conversation
Both halves of the protocol assumed the relay arrives. Two failure modes break that silently and neither was written down: 1. `pane send-text` / `agent send` type the text with no Enter, so a relay sits unsent in the recipient's composer — delivered from the sender's side, invisible from theirs. Only `pane run` supplies the Enter. Both cross-coordinate skills now carry a "Sending into another pane" section (run-vs-send-text table, prose-is-not-a-send, fire-and-forget, and the backtick / leading-slash string traps consolidated there), and set-workspace calls it out for the slash-command injection in Step 3. 2. Claude Code auto-fills a pane's ❯ composer with a machine-generated suggested prompt derived from that pane's last turn. A default `agent read --source visible` strips ANSI, so a ghost and real unsent operator input are byte-identical; only `--ansi` distinguishes them (ghost = faint \x1b[2m grey, real = stark white). Both skills now say never to read pane state or Operator intent from that line. The coordinator's Step 2a interruptibility test listed "an empty ❯ prompt" as a safe-idle signal, which is exactly the trap — it now classifies from the transcript plus agent_status only. README and EXAMPLE pick up short versions so the mechanics are visible before install. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01U5Rxy1AE7FV6RwmMFMbotY
There was a problem hiding this comment.
Pull request overview
This PR updates the herdr documentation templates to capture two cross-pane delivery “gotchas” encountered in live workspace driving: (1) text injection that doesn’t press Enter (so nothing actually submits), and (2) Claude Code’s auto-generated ghost suggestion in the ❯ composer line (which can be mistaken for real pending operator input). These clarifications are added to both cross-coordinate skills, set-workspace, and surfaced in the top-level README/EXAMPLE walkthrough.
Changes:
- Documented why
herdr pane run(text + Enter) must be used for relays, and howsend-text/agent sendcan strand unsent text in a composer. - Documented “ghost” suggested prompts in the
❯composer line and updated interruptibility guidance to rely on transcript +agent_status, not the composer line. - Added short call-outs in README.md and EXAMPLE.md to ensure these mechanics are treated as protocol fundamentals (not platform-specific).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| herdr/README.md | Adds a “Two delivery mechanics” section and clarifies platform-specific vs non-platform-specific gotchas. |
| herdr/EXAMPLE.md | Updates the walkthrough and takeaways to explicitly call out pane run and the ❯ ghost-suggestion trap. |
| herdr/coordinator/set-workspace/SKILL.md | Adds explicit guidance about Enter submission (pane run) and cautions against reading pane state from the ❯ line. |
| herdr/coordinator/cross-coordinate/SKILL.md | Adds a dedicated “Sending into a child pane” mechanics section and fixes interruptibility classification to ignore the composer line. |
| herdr/child/cross-coordinate/SKILL.md | Adds the parallel “Sending into another pane” mechanics section and ghost-suggestion guidance; refactors related outbound guidance accordingly. |
Suppressed comments (2)
herdr/README.md:77
- This reference to
agent read --ansiis missing theherdrprefix and pane context; the other docs useherdr agent read <pane> …, so readers may not know what to run here.
- **The `❯` composer line lies.** Claude Code auto-fills it with a machine-generated *suggested*
prompt derived from that pane's last turn. A plain-text read can't tell it from real unsent
operator input — only `agent read --ansi` can (ghosts are faint `\x1b[2m` grey; real input is
stark white). Never judge a pane's state, its interruptibility, or the Operator's intent from
that line.
herdr/child/cross-coordinate/SKILL.md:200
- The inline code span for the
herdr pane read …command is split across two lines, which breaks Markdown formatting (unterminated backtick on the first line) and makes the command hard to copy/paste.
`pane run` (not `send-text` / `agent send`) with a **single-line**, backtick-free message — see
*Sending into another pane* above for why each of those matters. It's fire-and-forget: nothing
confirms delivery, so if you want certainty the relay landed, `herdr pane read <coordinator-pane>
--source recent` rather than assuming.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - **`pane run`, always.** `herdr pane run <pane> "<text>"` types the text *and presses Enter*. | ||
| `pane send-text` and `agent send` write the characters with **no** Enter, stranding your relay | ||
| unsent in the recipient's composer: delivered from your side, invisible from theirs. Most | ||
| "the other pane ignored me" incidents are this. (herdr's own `agent --help` says it outright: | ||
| *agent send writes literal text; use pane run when you want command text plus Enter*.) |
| - **`pane run` is the only form that submits.** It types the text *and* presses Enter; | ||
| `pane send-text` / `agent send` write the characters with **no** Enter, leaving the slash | ||
| command sitting unexecuted in the composer. A pane that "ignored" its `/rename` was almost | ||
| always sent without an Enter. |
| tracking item says "relayed." Keep every relay to a **single line** so it submits cleanly; if one | ||
| genuinely must span lines, `pane send-text` the block then `herdr pane send-keys <pane> Enter` | ||
| **once**. |
| - Filter on the **`\x1b[2m`** code, not on the `❯` glyph — the glyph decodes to surrogate bytes and | ||
| won't match. |
| Keep every relay to a **single line** so it submits cleanly. If a send genuinely must span lines, | ||
| `pane send-text` the block and then `herdr pane send-keys <pane> Enter` **once** — but prefer one | ||
| line plus a pointer to the durable note. |
| - Filter on the **`\x1b[2m`** code, not on the `❯` glyph — the glyph decodes to surrogate bytes and | ||
| won't match. |
Two gotchas we hit driving the live workspace never made it into the template — surfaced by the migration to a new box.
1. Text without an Enter never submits
herdr pane send-text/agent sendtype the characters with no Enter, so a relay sits unsent in the recipient's composer: delivered from the sender's side, invisible from theirs. Onlyherdr pane runsupplies the Enter (herdr's ownagent --helpsays so outright).Both
cross-coordinateskills get a Sending into another pane section covering it, plus:pane read --source recent/string traps, consolidated there instead of repeated inlineset-workspacegets the same call-out for its slash-command injection (a pane that "ignored" its/renamewas almost always sent without an Enter).2. The
❯composer line liesClaude Code auto-fills a pane's composer with a machine-generated suggested prompt derived from that pane's last turn. A default
agent read --source visiblestrips ANSI, so a ghost and real unsent operator input are byte-identical — only--ansitells them apart (ghost = faint\x1b[2mgrey; real = stark white; match the SGR code, not the❯glyph, which decodes to surrogate bytes).Both skills now say never to read pane state or Operator intent from that line, and note that sending is unaffected —
pane runtypes over any placeholder, so there's nothing to clear first.This also fixes a live bug in the coordinator skill: Step 2a listed "an empty
❯prompt" as a safe-idle signal, which is exactly the trap. It now classifies interruptibility from the transcript plusagent_statusonly.Also
README.mdgets a short "Two delivery mechanics the protocol rests on" section (and a note that, unlike the Git Bash gotchas, these are not platform-specific), andEXAMPLE.mdpicks up a one-liner in the walkthrough and the takeaways.Docs only — no code.