Repository navigation
fix: submit Claude slash commands through Herdr completion popups - #111
Merged
Merged
Conversation
added 5 commits
October 10, 2026 01:52
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
/exit.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.
Evidence: Lab preparation refusal
Source: Lab preparation refusal
fm-herdr-lab: fleet-state tripwire requires exactly one running default sessionEvidence: Recorded-shape composer replay (non-live)
Source: Recorded-shape composer replay (non-live)
Known limitations
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.
/exit.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 throughFM_HERDR_SUBMIT_CONFIRM_LIVE=1 tests/fm-herdr-submit-confirm-live-e2e.test.shonce the lab is available.Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
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/filefollowed 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/exitand 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/filefollowed 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/exitand 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/exitin 38;2;112;112;112, bright popup rows and an arrow footer, selection retains the composer, but state classification strips/exitand 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/filefollowed 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/exitand 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/exitin 38;2;112;112;112, bright popup rows and an arrow footer, selection retains the composer, but state classification strips/exitand 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/compactremains in Claude's 38;2;112;112;112 styling, the now-absent popup disables this exception, strips the command, and returnsempty. 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 rowssolid 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-gtprevents the valid titled sandwich from replacing that proof. Popup recognition then fails againstearlier 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).TMPDIR="$PWD/.test-temp" bash tests/fm-composer-lib.test.shTMPDIR="$PWD/.test-temp" bash tests/fm-backend-herdr.test.shFM_HERDR_LAB_STATE_DIR="$PWD/.test-temp/herdr-state" bin/fm-herdr-lab.sh prepare "$SESSION"with a helper-generated fm-lab-* nameExecuted 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.