Skip to content

fix(herdr): stop Broken-pipe stderr noise on every capability probe - #1335

Open
wameson wants to merge 1 commit into
kunchenguid:mainfrom
wameson:fm/herdr-broken-pipe-noise
Open

fix(herdr): stop Broken-pipe stderr noise on every capability probe#1335
wameson wants to merge 1 commit into
kunchenguid:mainfrom
wameson:fm/herdr-broken-pipe-noise

Conversation

@wameson

@wameson wameson commented Jul 30, 2026

Copy link
Copy Markdown

Intent

Fix the 'printf: write error: Broken pipe' stderr noise the herdr backend writes on every capability check. Root cause: fm_backend_herdr_events_capable in bin/backends/herdr.sh piped the large API schema into 'grep -Fq'; grep -q exits on first match and closes the read end of the pipe while printf is still writing, so printf gets EPIPE and prints the broken-pipe message to stderr on essentially every watcher cycle. Fix: replace the two printf|grep -Fq lines with in-memory 'case $schema in token' substring globs - no subshell, no pipe, no early-closing reader, so no EPIPE, and faster. Behavior must NOT change: the case glob is semantically identical to grep -F (literal substring match), returning success only when BOTH events.subscribe and pane.agent_status_changed are present and return 1 otherwise; neither token contains a glob metacharacter so the match is equivalent - detection unchanged, not weakened. The one other pipe-into-grep in the file (a grep -qE regex on a single stripped terminal line) is a tiny buffer that never overflows the pipe, produces no such noise, and can't take the same substring fix, so it was left unchanged. Added three colocated regression tests in tests/fm-backend-herdr.test.sh proving both-tokens-present is capable, either-token-missing is not capable, and the probe leaves stderr clean; the clean-stderr test runs under 'trap "" PIPE' against a >64KiB schema and was verified to fail against the old grep-piped code and pass against the fix. Firstmate-repo change to shared tracked tooling following firstmate-coding-guidelines. Delivery is via the wameson/firstmate fork (no push access to upstream kunchenguid/firstmate); the PR must be opened by no-mistakes so it carries the required signature line.

What Changed

  • Replaced the two printf '%s' "$schema" | grep -Fq ... lines in fm_backend_herdr_events_capable (bin/backends/herdr.sh) with in-memory case "$schema" in *token* substring globs, so no subshell or pipe is created and grep -q can no longer close the read end early and trigger printf: write error: Broken pipe on stderr each watcher cycle. Detection is unchanged: the probe still succeeds only when both events.subscribe and pane.agent_status_changed are present.
  • Added three colocated regression tests in tests/fm-backend-herdr.test.sh covering both-tokens-present (capable), a token missing (not capable), and a clean-stderr probe run under trap "" PIPE against a >64KiB schema; the stderr test was verified to fail against the old grep-piped code and pass against the fix.

Risk Assessment

✅ Low: A well-bounded, single-purpose fix that replaces two pipe-into-grep substring checks with semantically identical in-memory case globs, with three colocated regression tests that correctly reproduce and pin the EPIPE noise.

Testing

Exercised the herdr capability-probe fix end-to-end. The full tests/fm-backend-herdr.test.sh suite passes (exit 0) with the three new colocated regression tests. I proved the fix matters and the test is meaningful by reproducing the exact end-user symptom — a 240KiB schema under trap "" PIPE makes the old printf | grep -Fq path emit printf: write error: Broken pipe twice to stderr, while the new case "$schema" in *TOKEN* globs leave stderr clean — and by reverting herdr.sh to the old code, which flips the suite to a failing not ok - capability probe emitted 'Broken pipe' (exit 1). Detection is confirmed unchanged (capable iff both tokens present). This is a shell/CLI-only change with no user-visible UI surface, so a CLI/stderr transcript is the appropriate evidence rather than a screenshot. Worktree restored clean; no transient artifacts left behind.

Evidence: Old-vs-new stderr behavior (240KiB schema under trap '' PIPE)
schema size (bytes): 240043

=== OLD code (printf | grep -Fq) ===
stderr: bash: printf: write error: Broken pipe (x2, one per token)

=== NEW code (case glob) ===
stderr: <empty (clean)>
Evidence: Regression test catches old code (suite run against reverted herdr.sh)
suite exit=1
not ok - capability probe emitted 'Broken pipe' on stderr: .../bin/backends/herdr.sh: line 2559: printf: write error: Broken pipe
.../bin/backends/herdr.sh: line 2560: printf: write error: Broken pipe
Evidence: Full evidence transcript
herdr Broken-pipe capability-probe fix — end-to-end evidence
============================================================

SYMPTOM REPRODUCTION (240KiB schema, under `trap "" PIPE` like the real watcher)

  OLD code — `printf '%s' "$schema" | grep -Fq TOKEN`:
    bash: line 3: printf: write error: Broken pipe
    bash: line 4: printf: write error: Broken pipe
      (one line PER token PER probe — emitted on essentially every watcher cycle)

  NEW code — `case "$schema" in *TOKEN*) ;; *) return 1 ;; esac`:
    <stderr empty — clean>

DETECTION UNCHANGED (bin/backends/herdr.sh fm_backend_herdr_events_capable):
  both tokens present            -> capable (rc 0)
  events.subscribe missing       -> not capable (rc 1)
  pane.agent_status_changed miss -> not capable (rc 1)

REGRESSION TEST VALIDATED BOTH DIRECTIONS (tests/fm-backend-herdr.test.sh):
  against the FIX  : ok - ...both tokens present -> capable, with no Broken-pipe stderr noise
                     ok - ...missing events.subscribe -> not capable
                     ok - ...missing pane.agent_status_changed -> not capable
                     (full suite exit 0)
  against OLD code : not ok - capability probe emitted 'Broken pipe' on stderr:
                       .../bin/backends/herdr.sh: line 2559: printf: write error: Broken pipe
                       .../bin/backends/herdr.sh: line 2560: printf: write error: Broken pipe
                     (full suite exit 1)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • bash tests/fm-backend-herdr.test.sh — full suite passes (exit 0), including the three new test_events_capable_* cases
  • Reverted herdr.sh to the old printf | grep -Fq code and re-ran the suite: it fails (exit 1) with not ok - capability probe emitted &#39;Broken pipe&#39; on stderr, proving the regression test catches the bug; then restored the fix and confirmed git status --porcelain clean
  • Direct old-vs-new symptom reproduction under trap &#34;&#34; PIPE against a 240KiB schema: old code prints printf: write error: Broken pipe twice, new case-glob code produces empty/clean stderr
✅ **Document** - passed

✅ No issues found.

⚠️ **Lint** - 1 warning
  • ⚠️ linter found issues (exit code 127)
✅ **Push** - passed

✅ No issues found.

fm_backend_herdr_events_capable piped the (large) API schema into
`grep -Fq`. grep -q exits on first match and closes the read end of the
pipe while printf is still writing, so printf gets EPIPE and prints
`printf: write error: Broken pipe` to stderr on essentially every
watcher cycle.

Replace the two pipe-into-grep tests with `case` globs that match the
in-memory schema variable directly. No subshell, no pipe, no reader that
can close early, so no EPIPE; `*literal*` is a literal substring match
equivalent to grep -F, so capability detection is behaviorally identical
(capable only when both events.subscribe and pane.agent_status_changed
are present) and not weakened. The only other pipe-into-grep in the file
(the bare-prompt regex on a single stripped terminal line) is a regex
match on a tiny buffer that never overflows the pipe, so it produces no
such noise and is left unchanged.

Add colocated regression tests proving both-tokens-present is capable,
either-token-missing is not capable, and the probe leaves stderr clean
(the tests run under `trap '' PIPE`, the disposition under which the
noise actually surfaces, and fail against the old grep-piped code).
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