Skip to content

test: eliminate SIGPIPE flake in grep-q assertions (fixes intermittent CI) - #101

Merged
aimsise merged 1 commit into
mainfrom
ci/fix-sigpipe-assert-flake
Jun 28, 2026
Merged

test: eliminate SIGPIPE flake in grep-q assertions (fixes intermittent CI)#101
aimsise merged 1 commit into
mainfrom
ci/fix-sigpipe-assert-flake

Conversation

@aimsise

@aimsise aimsise commented Jun 28, 2026

Copy link
Copy Markdown
Owner

Problem

The Tests CI job failed intermittently with a FALSE assertion failure — most recently test-post-ship-state-auto-compact.sh AC-2: serial early-exit guarded by the off branch, where the substring was present but the assertion reported FAIL. The CI log showed the tell:

tests/test-post-ship-state-auto-compact.sh: line 84: printf: write error: Broken pipe
  FAIL AC-2: serial early-exit guarded by the off branch

Root cause (SIGPIPE under pipefail): assert_contains / assert_not_contains (duplicated in 5 files) and many inline checks used:

printf '%s' "$haystack" | grep -qF -- "$needle"   # under set -euo pipefail

With a large (file-sized) $haystack, printf writes in stdio chunks; grep -q matches early and exits, closing the pipe; printf's next write gets SIGPIPE → exits 141 → pipefail makes the whole pipeline non-zero → the assertion falsely fails even though the match is correct. Timing-dependent, so the same test code passed on the v10.0.0 and PR #100 runs and broke this one.

Fix

Convert every racy 2-stage producer | grep -q… to a pipe-free, SIGPIPE-safe equivalent (behavior-preserving — grep -q's found/not-found is identical, and a here-string never changes a substring/line match):

printf '%s' "$X" | grep -q… -- "$N"   ->   grep -q… -- "$N" <<<"$X"
cat FILE          | grep -q… "$N"     ->   grep -q… -- "$N" FILE
CMD               | grep -q… "$N"     ->   h=$(CMD); grep -q… -- "$N" <<<"$h"

323 occurrences across 30 test files, including all 5 assert_contains + 3 assert_not_contains definitions + assert_blocked_message. grep flags (-qF/-qE/-qiF/-qx) preserved; -- added before patterns.

Left untouched (not racy): non--q grep (grep -c / grep -o, read to EOF — no early-exit race), multi-stage pipes, and short bounded producers (head -1).

Independent of the v10.0.0 / swap-headroom changes (the swap fix touched only the ShellCheck CI job; this touches only tests/).

Verification

  • tests/run-all.sh ALL TEST SUITES PASSED — every suite's pass count byte-identical to baseline (test-skill-contracts 915/915, test-path-consistency 145/145, test-ask-guard 56/56, …).
  • shellcheck --severity=warning clean (CI match) — the 323 conversions introduce no warning.
  • Only tests/ touched (no agents / hooks / skills). Each edited file independently re-ran green (or bash -n for the two claude -p files).

🤖 Generated with Claude Code

https://claude.ai/code/session_01Mw2bH4wbEPeebXsvSG6rWe

…t CI)

The Tests CI job failed intermittently — most recently
test-post-ship-state-auto-compact.sh AC-2 — with a FALSE assertion failure.
Root cause: assert_contains / assert_not_contains (duplicated in 5 files) and
many inline checks used `printf '%s' "$haystack" | grep -qF -- "$needle"` under
`set -euo pipefail`. With a large (file-sized) haystack, printf writes in stdio
chunks; grep -q matches early and exits, closing the pipe; printf's next write
gets SIGPIPE ("printf: write error: Broken pipe") and exits 141; pipefail
propagates that as the pipeline status, so the assertion FALSELY FAILS even
though the match result is correct. It is timing-dependent, so it passed on most
runs and broke others (same test code passed on the v10.0.0 and PR #100 runs).

Convert every racy 2-stage `producer | grep -q...` to a pipe-free, SIGPIPE-safe
equivalent (behavior-preserving; grep -q's found/not-found is identical):
  printf '%s' "$X" | grep -q... -- "$N"  ->  grep -q... -- "$N" <<<"$X"
  cat FILE          | grep -q... "$N"    ->  grep -q... -- "$N" FILE
  CMD               | grep -q... "$N"    ->  h=$(CMD); grep -q... -- "$N" <<<"$h"

323 occurrences across 30 test files, including all 5 assert_contains + 3
assert_not_contains definitions + assert_blocked_message. grep flags
(-qF/-qE/-qiF/-qx) preserved exactly; `--` added before patterns. Left untouched:
non-`-q` grep (grep -c / grep -o, read to EOF — no early-exit race), multi-stage
pipes, and short bounded producers (head -1).

This is a pre-existing flake, independent of the v10.0.0 / swap-headroom changes
(the swap fix only touched the ShellCheck CI job; this touches only tests/).

Verification: tests/run-all.sh ALL TEST SUITES PASSED (every suite's pass count
byte-identical to baseline — test-skill-contracts 915/915, etc.); shellcheck
--severity=warning clean (CI match); only tests/ touched (no agents/hooks/skills).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mw2bH4wbEPeebXsvSG6rWe
@aimsise
aimsise merged commit 2908297 into main Jun 28, 2026
2 checks passed
@aimsise
aimsise deleted the ci/fix-sigpipe-assert-flake branch June 28, 2026 14:49
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