Skip to content

fix: submit Claude slash commands through Herdr completion popups - #111

Merged
andrewesweet merged 5 commits into
mainfrom
fm/fm-herdr-slash-composer
Oct 10, 2026
Merged

andrewesweet merged 5 commits into
mainfrom
fm/fm-herdr-slash-composer

Conversation

@andrewesweet

@andrewesweet andrewesweet commented Oct 10, 2026 •

Copy link
Copy Markdown
Owner

Intent

Fix Herdr submission of slash-command payloads to Claude panes: the composer read currently includes Claude's slash-command completion popup rows, so the payload-shown check fails and commands such as /exit are judged unsent, which breaks restarting Claude second mates on Herdr. The composer read or payload check is corrected at its shared root and covered by a regression test using the rendered popup shape.

What Changed

  • Correct shared composer selection to exclude completion popup rows and status footers, allowing Herdr to prove and submit Claude slash commands such as /exit.
  • Preserve grey typed commands backed by popup evidence and wrapped literal text, while retaining activity and stale-composer guards.
  • Add rendered-popup regressions for submission, pending state, Enter retries, and partial-payload refusal; document popup shapes and live verification limits.

Trigger

Compared fork main at 484dce4 against the sync merge cccc756 (PR #108): the composer read path (fm_backend_herdr_composer_content, fm_backend_herdr_composer_payload_shown, the capture helpers, fm_composer_extract_selected_content) is byte-identical across that range.
PR #108 only added the blocking-dialog picker gate and a per-uid lock namespace.
The change that made popup rows reach the payload check is upstream commit 050a446 (full-viewport composer reads, upstream kunchenguid#5876), which switched Herdr composer reads from a bounded tail to the full visible viewport.
The defective code is upstream code, present unchanged on kunchenguid/firstmate main - not fork-only.

Risk Assessment

Residual risk is bounded to the two known edge cases under Known limitations.
On every other recorded shape the shared selection now extracts exactly the typed command, including arrow-led status lines, grey-styled commands, and wrapped slash-leading literals (see the recorded-shape replay under Testing).

Testing

Both focused test files passed, including popup and tail-refusal regressions. Executable fixture replay reproduced incorrect extraction on the base and correct extraction on the target. Lab preparation was refused, so no live scenarios or screenshots could be captured; diagnostic and replay logs were retained.

  • Live validation: ⚠️ inconclusive - 0 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Submit /exit through Herdr with completion rows directly below the composer; Claude exits. ⏸️ untested no The required Herdr lab safety tripwire refused preparation. An operator must ensure exactly one running default session, then rerun Test.
Type /exit above popup rows and an arrow statusLine; composer extraction returns only /exit, including inside a titled composer. ⏸️ untested no The required isolated Herdr lab could not be prepared. Restore its default-session prerequisite and rerun Test.
Submit a grey slash command after Enter is swallowed; it remains pending and receives another Enter. ⏸️ untested no Lab preparation was refused by the Herdr safety tripwire. Restore the required default-session state and rerun Test against real Claude.
Submit wrapped slash-leading literal text; the complete payload remains intact. ⏸️ untested no The required Herdr lab could not start because its default-session prerequisite was unverified. An operator must restore it and rerun Test.
Attempt submission when only a payload tail is visible above the popup; submission is refused and the leftover is cleared. ⏸️ untested no The required isolated Herdr lab was blocked by its safety tripwire. Restore the default-session prerequisite and rerun Test.
Evidence: Lab preparation refusal

Source: Lab preparation refusal

fm-herdr-lab: fleet-state tripwire requires exactly one running default session

fm-herdr-lab: fleet-state tripwire requires exactly one running default session
Evidence: Recorded-shape composer replay (non-live)

Source: Recorded-shape composer replay (non-live)

Recorded-shape executable replay; this is fixture evidence, not live validation.
Baseline c7976a2:
direct-popup: content=/exit\ /exit\ Exit\ the\ CLI\ /context\ Show\ context; state=pending
arrow-footer: content=repo\ git:\(main\)\ ⏵⏵\ bypass\ permissions\ on; state=pending
grey-command: content=repo\ git:\(main\)\ ⏵⏵\ bypass\ permissions\ on; state=pending
wrapped-literal: content=/repo/file\ /repo/file\ needs\ changes; state=pending

Target fd939e7:
direct-popup: content=/exit; state=pending
arrow-footer: content=/exit; state=pending
grey-command: content=/exit; state=pending
wrapped-literal: content=/repo/file\ /repo/file\ needs\ changes; state=pending
- Outcome: ⚠️ 2 warnings across 1 run (3m56s)

Known limitations

  • Popup-dismissal transition: the first Enter can close the completion popup without submitting.
    If the typed command remains in Claude's grey 38;2;112;112;112 styling after the popup closes, state classification can strip it and read the composer as empty, so a swallowed Enter could be falsely confirmed instead of retried.
  • Titled-session tie-break: when a titled composer's closing rule is also the closing rule of an earlier transcript envelope, the separator scan keeps the earlier proof, popup recognition fails against the older text, and the arrow-led status line below can win selection, rejecting and clearing the typed /exit.
  • Live validation has not been run: the Herdr lab's safety tripwire refused preparation because the fleet laptop's default Herdr session is not running (restoring it is an operator action).
    All five live scenarios under Testing are untested; the portable regressions and the recorded-shape replay pass.

The two composer edge cases are filed as follow-up task fm-herdr-composer-edge-cases; the live claim refreshes through FM_HERDR_SUBMIT_CONFIRM_LIVE=1 tests/fm-herdr-submit-confirm-live-e2e.test.sh once the lab is available.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 6 errors
  • 🚨 bin/fm-composer-lib.sh:1413 - Spec: Repeated slash-leading input is mistaken for popup furniture. A literal payload /repo/file /repo/file needs changes, wrapped into ❯ /repo/file followed by /repo/file needs changes, matches this predicate and extracts only the first row. Herdr then rejects and clears the complete payload; Zellij's append proof also rejects it. Require evidence distinguishing popup rows from real continuation text. Related changed sites: bin/fm-composer-lib.sh:1414 (prefix match), bin/fm-composer-lib.sh:1585 (first-row head), bin/fm-composer-lib.sh:1600 (truncation), and docs/verification/runtime-backends.md:1275 (incorrect assertion that continuations never repeat their head).
  • 🚨 bin/fm-composer-lib.sh:1600 - Spec: Claude's supported arrow-led statusLine still bypasses the fix. With ❯ /exit, popup rows, then → repo git:(main) and the permission footer, scanning selects the lower arrow as the bare composer. The new shape has no closing envelope, so footer demotion cannot apply; this cutoff never examines the popup above it. Herdr therefore reads status text instead of /exit and retains the restart failure. Correct selection at the shared boundary before choosing the bottommost glyph. Related changed sites: bin/fm-composer-lib.sh:1585 (wrong head), tests/fm-backend-herdr.test.sh:5224 (fixture omits the supported statusLine), and docs/verification/runtime-backends.md:1274 (shared-selection claim).

🔧 Fix applied.
4 errors still open:

  • 🚨 bin/fm-composer-lib.sh:1413 - Spec: Repeated slash-leading input is mistaken for popup furniture. A literal payload /repo/file /repo/file needs changes, wrapped into ❯ /repo/file followed by /repo/file needs changes, matches this predicate and extracts only the first row. Herdr then rejects and clears the complete payload; Zellij's append proof also rejects it. Require evidence distinguishing popup rows from real continuation text. Related changed sites: bin/fm-composer-lib.sh:1414 (prefix match), bin/fm-composer-lib.sh:1585 (first-row head), bin/fm-composer-lib.sh:1600 (truncation), and docs/verification/runtime-backends.md:1275 (incorrect assertion that continuations never repeat their head).
  • 🚨 bin/fm-composer-lib.sh:1600 - Spec: Claude's supported arrow-led statusLine still bypasses the fix. With ❯ /exit, popup rows, then → repo git:(main) and the permission footer, scanning selects the lower arrow as the bare composer. The new shape has no closing envelope, so footer demotion cannot apply; this cutoff never examines the popup above it. Herdr therefore reads status text instead of /exit and retains the restart failure. Correct selection at the shared boundary before choosing the bottommost glyph. Related changed sites: bin/fm-composer-lib.sh:1585 (wrong head), tests/fm-backend-herdr.test.sh:5224 (fixture omits the supported statusLine), and docs/verification/runtime-backends.md:1274 (shared-selection claim).
  • 🚨 bin/fm-composer-lib.sh:1609 - Spec: Excluding popup rows exposes a false-empty verdict for Claude’s documented grey command styling. With /exit in 38;2;112;112;112, bright popup rows and an arrow footer, selection retains the composer, but state classification strips /exit and treats the surviving glyph as empty. After a swallowed Enter with native status still idle, Herdr falsely confirms delivery instead of retrying. Round 1’s footer fix extends this regression to arrow-footer screens. Preserve proven typed slash input in the shared state classifier. Related changed sites: bin/fm-composer-lib.sh:1460 (popup proof), bin/fm-composer-lib.sh:1483 (closed-container popup), bin/fm-composer-lib.sh:1491 (menu exclusion); tests/fm-backend-herdr.test.sh:5217 (fixture omits command styling), tests/fm-composer-lib.test.sh:1062 (styling coverage omits grey).
  • 🚨 bin/fm-composer-lib.sh:1459 - Spec: Round 1 leaves the supported titled-composer sibling behind. With a named-session top rule, ❯ /exit, its solid closing rule, popup rows, arrow statusLine and permission footer, no separator pair is recorded. The loop forgets the slash head at the closing rule and selection extracts the arrow’s status text; Herdr rejects and clears /exit, retaining the restart failure. Recognize the existing titled-rule sandwich at the shared footer boundary and apply its staleness proof to the selected composer. Related sites: bin/fm-composer-lib.sh:1465 (head reset), bin/fm-composer-lib.sh:1596 (wrong selected head), bin/fm-composer-lib.sh:1609 (cutoff examines wrong candidate), bin/fm-composer-lib.sh:1583 (existing exception still uses the original bottommost glyph); tests/fm-composer-lib.test.sh:1054 (omitted titled shape), docs/verification/runtime-backends.md:1276 (closing-rule coverage claim).

🔧 Fix applied.
6 errors still open:

  • 🚨 bin/fm-composer-lib.sh:1413 - Spec: Repeated slash-leading input is mistaken for popup furniture. A literal payload /repo/file /repo/file needs changes, wrapped into ❯ /repo/file followed by /repo/file needs changes, matches this predicate and extracts only the first row. Herdr then rejects and clears the complete payload; Zellij's append proof also rejects it. Require evidence distinguishing popup rows from real continuation text. Related changed sites: bin/fm-composer-lib.sh:1414 (prefix match), bin/fm-composer-lib.sh:1585 (first-row head), bin/fm-composer-lib.sh:1600 (truncation), and docs/verification/runtime-backends.md:1275 (incorrect assertion that continuations never repeat their head).
  • 🚨 bin/fm-composer-lib.sh:1600 - Spec: Claude's supported arrow-led statusLine still bypasses the fix. With ❯ /exit, popup rows, then → repo git:(main) and the permission footer, scanning selects the lower arrow as the bare composer. The new shape has no closing envelope, so footer demotion cannot apply; this cutoff never examines the popup above it. Herdr therefore reads status text instead of /exit and retains the restart failure. Correct selection at the shared boundary before choosing the bottommost glyph. Related changed sites: bin/fm-composer-lib.sh:1585 (wrong head), tests/fm-backend-herdr.test.sh:5224 (fixture omits the supported statusLine), and docs/verification/runtime-backends.md:1274 (shared-selection claim).
  • 🚨 bin/fm-composer-lib.sh:1609 - Spec: Excluding popup rows exposes a false-empty verdict for Claude’s documented grey command styling. With /exit in 38;2;112;112;112, bright popup rows and an arrow footer, selection retains the composer, but state classification strips /exit and treats the surviving glyph as empty. After a swallowed Enter with native status still idle, Herdr falsely confirms delivery instead of retrying. Round 1’s footer fix extends this regression to arrow-footer screens. Preserve proven typed slash input in the shared state classifier. Related changed sites: bin/fm-composer-lib.sh:1460 (popup proof), bin/fm-composer-lib.sh:1483 (closed-container popup), bin/fm-composer-lib.sh:1491 (menu exclusion); tests/fm-backend-herdr.test.sh:5217 (fixture omits command styling), tests/fm-composer-lib.test.sh:1062 (styling coverage omits grey).
  • 🚨 bin/fm-composer-lib.sh:1459 - Spec: Round 1 leaves the supported titled-composer sibling behind. With a named-session top rule, ❯ /exit, its solid closing rule, popup rows, arrow statusLine and permission footer, no separator pair is recorded. The loop forgets the slash head at the closing rule and selection extracts the arrow’s status text; Herdr rejects and clears /exit, retaining the restart failure. Recognize the existing titled-rule sandwich at the shared footer boundary and apply its staleness proof to the selected composer. Related sites: bin/fm-composer-lib.sh:1465 (head reset), bin/fm-composer-lib.sh:1596 (wrong selected head), bin/fm-composer-lib.sh:1609 (cutoff examines wrong candidate), bin/fm-composer-lib.sh:1583 (existing exception still uses the original bottommost glyph); tests/fm-composer-lib.test.sh:1054 (omitted titled shape), docs/verification/runtime-backends.md:1276 (closing-rule coverage claim).
  • 🚨 bin/fm-composer-lib.sh:1901 - Spec: Round 2's grey-command fix leaves the documented popup-dismissal transition behind. The first Enter can close completion without submitting (tests/fm-backend-herdr.test.sh:4493). If /compact remains in Claude's 38;2;112;112;112 styling, the now-absent popup disables this exception, strips the command, and returns empty. With native status still idle, Herdr falsely confirms delivery instead of sending the required second Enter. Preserve recognized typed slash input at the shared content-classification boundary after popup dismissal. Related changed sites: bin/fm-composer-lib.sh:1833 (cursor classification), bin/fm-composer-lib.sh:1676 (extraction); tests/fm-backend-herdr.test.sh:5270 (retry fixture retains the popup), tests/fm-composer-lib.test.sh:1099 (grey cases always include a menu), docs/verification/runtime-backends.md:1279 (retry claim).
  • 🚨 bin/fm-composer-lib.sh:1470 - Spec: Round 2 leaves titled-composer selection dependent on preceding transcript history. For rows solid rule, ❯ earlier request, matching titled rule, ❯ /exit, solid closing rule, popup rows, arrow statusLine and permission footer, the separator scan records the earlier request as its proof. Both envelopes share the closing row, so this strict -gt prevents the valid titled sandwich from replacing that proof. Popup recognition then fails against earlier request, the arrow wins, and Herdr rejects and clears /exit. Prefer the proven adjacent titled sandwich when closing rows tie. Related changed sites: bin/fm-composer-lib.sh:1483 (wrong proof head), bin/fm-composer-lib.sh:1656 (selected popup proof); tests/fm-composer-lib.test.sh:1057 and :1093 (history-free titled fixtures), tests/fm-backend-herdr.test.sh:5289 (same omission), docs/verification/runtime-backends.md:1276 (selection claim).
⚠️ **Test** - 2 warnings
  • ⚠️ The test phase cannot demonstrate live Herdr slash-command delivery: lab preparation refused because its safety tripwire could not verify exactly one running default session. An operator must restore that prerequisite safely, then rerun this phase. Focused regressions passed, but they do not establish live delivery.
  • ⚠️ live validation verdict: inconclusive (0 of 5 scenarios were driven live against the product); untested: Submit /exit through Herdr with completion rows directly below the composer; Claude exits., Type /exit above popup rows and an arrow statusLine; composer extraction returns only /exit, including inside a titled composer., Submit a grey slash command after Enter is swallowed; it remains pending and receives another Enter., Submit wrapped slash-leading literal text; the complete payload remains intact., Attempt submission when only a payload tail is visible above the popup; submission is refused and the leftover is cleared.
  • Live validation: ⚠️ inconclusive - 0 of 5 scenarios driven live against the product
Scenario Result Live Evidence
Submit /exit through Herdr with completion rows directly below the composer; Claude exits. ⏸️ untested no The required Herdr lab safety tripwire refused preparation. An operator must ensure exactly one running default session, then rerun Test.
Type /exit above popup rows and an arrow statusLine; composer extraction returns only /exit, including inside a titled composer. ⏸️ untested no The required isolated Herdr lab could not be prepared. Restore its default-session prerequisite and rerun Test.
Submit a grey slash command after Enter is swallowed; it remains pending and receives another Enter. ⏸️ untested no Lab preparation was refused by the Herdr safety tripwire. Restore the required default-session state and rerun Test against real Claude.
Submit wrapped slash-leading literal text; the complete payload remains intact. ⏸️ untested no The required Herdr lab could not start because its default-session prerequisite was unverified. An operator must restore it and rerun Test.
Attempt submission when only a payload tail is visible above the popup; submission is refused and the leftover is cleared. ⏸️ untested no The required isolated Herdr lab was blocked by its safety tripwire. Restore the default-session prerequisite and rerun Test.
  • TMPDIR="$PWD/.test-temp" bash tests/fm-composer-lib.test.sh
  • TMPDIR="$PWD/.test-temp" bash tests/fm-backend-herdr.test.sh
  • FM_HERDR_LAB_STATE_DIR="$PWD/.test-temp/herdr-state" bin/fm-herdr-lab.sh prepare "$SESSION" with a helper-generated fm-lab-* name
  • Executed composer extraction and classification against recorded popup, arrow-footer, grey-command, and wrapped-literal shapes using both the base and target libraries.
  • Removed temporary test files and confirmed a clean working tree.
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@andrewesweet
andrewesweet merged commit 56aeb21 into main Oct 10, 2026
20 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