fix(herdr): match bare composer prompt glyphs by alternation, not a bracket class#647
Open
zachlandes wants to merge 3 commits into
Open
fix(herdr): match bare composer prompt glyphs by alternation, not a bracket class#647zachlandes wants to merge 3 commits into
zachlandes wants to merge 3 commits into
Conversation
* Under a byte-oriented locale (LC_CTYPE=C) grep walks a [❯›] bracket class byte-wise, so it matches any row whose first byte is 0xE2 - including the box-drawing composer borders ╭ ╰ │ - and misreads a bordered composer as a bare prompt, classifying it 'pending' instead of 'empty'. The (❯|›) alternation matches each full glyph sequence and is correct in any locale. * Surfaced deterministically in the no-mistakes gate's C-locale test run, where it failed the bare-prompt composer-state assertion in tests/fm-backend-herdr.test.sh. cmux is unaffected (it classifies via locale-safe shell case globs, not this grep). * Keep docs/configuration.md's documented default in sync and note the alternation-not-bracket-class requirement for any override.
5 tasks
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.
What Changed
FM_BACKEND_HERDR_BARE_PROMPT_REnow defaults to the alternation'^(❯|›)'rather than the bracket class'^[❯›]'.Under a byte-oriented locale (
LC_CTYPE=C, which the supervise daemon inherits) grep walks a bracket class byte-wise, so[❯›]matched any row whose first byte was0xE2— including the box-drawing glyphs╭ ╰ │.Because
fm_backend_herdr_composer_statelets the lowest matching row win, a bordered composer's own closing border overwrote the correctshape=borderedverdict and was carried in as content: an empty composer readpending(away-mode defers delivery), and a dark-themed composer holding real typed text readempty(away-mode injects over an unsubmitted draft).The alternation matches each full glyph sequence, so it is correct in any locale and matches a strict subset of the old pattern's matches.
tests/fm-backend-herdr.test.shadds threecomposer_statecases that pinLC_ALL=Cexplicitly, which no composer case did before.Two are the regression pins and cover both failure directions (empty bordered composer reads
empty; a dark-themed composer with real text readspending); the third pins that the alternation still matches each full unbordered❯/›prompt glyph, so a pattern matching nothing could not satisfy the other two.docs/herdr-backend.mdrecords the 2026-07-16 incident and a known-gap note, anddocs/configuration.mdwarns that an override of this knob must be spelled as an alternation.The gap: the composer's two
grepcall sites (fm_backend_herdr_composer_stateandfm_composer_idle_matchesinbin/fm-composer-lib.sh) still inherit the ambient locale, unlikefm_composer_strip_ansiandfm_composer_strip_ghostwhich pinLC_ALL=C.The shipped defaults are locale-invariant, so fleet behavior is correct, but an operator override of
FM_BACKEND_HERDR_BARE_PROMPT_REorFM_BACKEND_HERDR_IDLE_REstill carries locale-dependent semantics; pinning those call sites is deferred to a separate change against the shared lib.Risk Assessment
✅ Low: The one-line fix is verified correct and matches a strict subset of the old pattern's matches (removing only false positives, so no previously-correct read can change), and the follow-up commit adds locale-pinned regression tests that I traced as genuinely failing on the old regex and passing on the new one, with scope held exactly to the captain's deferral decision.
Testing
Completed 1 recorded test check.
Pipeline
Updates from git push no-mistakes
⏭️ **intent** - skipped
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed ✅
bin/backends/herdr.sh:746- The fix ships no regression test, and the existing suite cannot distinguish it from the bug. No herdr/composer test pins a locale, so everything runs under ambient UTF-8 where the old '^[❯›]' and new '^(❯|›)' behave identically — which is exactly how this regression shipped. The coverage is already written but runs in the wrong locale: the fixture at tests/fm-backend-herdr.test.sh:818 (' ╭───╮ / │ ❯ │ / ╰──── Composer ───╯') is the precise triggering shape, and under LC_ALL=C with /usr/bin/grep the old regex makes it return 'pending' where the test asserts 'empty'. Adding a composer_state case that runs under LC_ALL=C would fail on the old regex and pass on the new one. This also departs from the repo's own convention for this area: every prior composer fix (0eaf293, a955a05, 1811b89) shipped a regression test in tests/ plus a docs/herdr-backend.md entry.bin/backends/herdr.sh:839- This grep is the only text-processing step in the composer pipeline that does not pin a locale — fm_composer_strip_ansi pins 'LC_ALL=C sed' (fm-composer-lib.sh:61) and fm_composer_strip_ghost pins 'LC_ALL=C awk' (fm-composer-lib.sh:87), both with comments explaining why. Inheriting the ambient locale here is what made this bug a heisenbug: UTF-8 in an interactive shell (works), C under the daemon (broken). The alternation makes the default pattern locale-invariant, but FM_BACKEND_HERDR_BARE_PROMPT_RE is an advertised override knob, so a user-supplied pattern still has locale-dependent semantics; the same applies to FM_BACKEND_HERDR_IDLE_RE via the unpinned grep in fm_composer_idle_matches (fm-composer-lib.sh:177). Pinning LC_ALL=C at the call site would make override behavior deterministic and fail visibly in dev rather than only in the daemon. Noting rather than pushing: the author's comment ('correct in any locale') plus the docs warning is a defensible alternative design.🔧 Fix: add C-locale composer regression tests and incident entry
✅ Re-checked - no issues remain.
✅ **Test** - passed
✅ No issues found.
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.