Under `set -o pipefail` a grep that MATCHES can make the pipeline exit 141:
grep closes the pipe on its first hit, the producer takes SIGPIPE, and pipefail
reports that as failure. A match becomes an error. Security found the shape;
measured here before believing it, because the earlier pipefail lesson went the
other way and I did not want to carry a second claim I had not seen:
yes hittet | grep -q hittet under pipefail -> exit 141
printf hittet | grep -q hittet -> exit 0
So it needs the producer to outrun the 64 KB pipe buffer, which is why it is
invisible until it is not.
Two of this repo's sites can get there, and both were converted. Neither is
reachable today — that is the reason to do it now rather than an argument
against:
ci/publish-release.sh checked the release feed for a download URL. The feed is
3.5 KB in the new repo and 13.5 KB in the old one across seven releases, so
about 1.9 KB each: it reaches 64 KB around release 34. That failure would land
in the publishing path and say the feed has no zip attached while it plainly
has one, and nothing about the commit that tripped it would have anything to do
with it. A bug on a timer is worse than a bug.
ci/static.sh checked whether each plugin-relative include is in the shipped
list, once per include. Same growth, plus a fork per iteration.
Both now use `case`, which has no pipe, no fork and no producer to signal, and
cannot be reversed by a shell option. The static.sh one wraps both sides in
newlines to keep the exact-line match `grep -qx` gave. Proven both ways:
thirteen includes checked on a clean tree, and excluding one from the zip still
names it.
The remaining `| grep -q` sites were measured rather than assumed safe:
zip_entries is 1.3 KB, the shipped list 478 bytes, static.sh's own output
1.6 KB, and site-test.sh and update-smoke.sh grep a single line of wp-cli
output. Left alone; converting those would be churn against producers that
cannot plausibly reach the buffer.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>