From bf9887b6ba0befd5c1584b7837f0415cdfa70383 Mon Sep 17 00:00:00 2001 From: amin Date: Sun, 13 Sep 2026 20:18:47 -0400 Subject: [PATCH] ci: remove pipefail/SIGPIPE races from shell pipelines MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `grep -q` (also `head -N`, `grep -m1`) stops reading stdin the moment it is satisfied. The stage upstream then takes EPIPE/SIGPIPE and exits 141, and under `set -o pipefail` that makes the whole pipeline non-zero **even though the consumer succeeded** — a confident, specific, wrong failure. Where pipefail is actually in effect: * a script with `set -o pipefail` / `set -euo pipefail`; * any workflow step with an explicit `shell: bash`, which GitHub runs as `bash --noprofile --norc -eo pipefail {0}`. The *unspecified* default is `bash -e {0}` with no pipefail, which is why this hides for so long. Not theoretical. marka's `scripts/check-root-allowlist.sh:25` reported `lighthouserc-docs.cjs` as "root file not in allowlist" on a commit where that file was both present and allowlisted, alongside `printf: write error: Broken pipe`. Reproduced 400/400 runs against a 200k-line producer while the match was present. What changed here — the pipe is removed; the failure is **not** masked: * `PRODUCER | grep -q P` -> `grep -q P <<<"$(PRODUCER)"`, collapsing to `<<<"$VAR"` where the producer was only `echo`/`printf` of a variable; * `... | head -N` -> `... | awk 'NR<=N'`. awk never exits early, so the upstream stage never takes SIGPIPE, and a *real* producer failure is still caught by pipefail; * allowlist membership done in-process rather than `printf ... | grep -qx`. `|| true` was deliberately not used: it would also swallow genuine failures. Verified before opening: every touched file passes `bash -n` (for workflows, a YAML parse plus `bash -n` on each `run:` block) and contains zero CR bytes. Old and new forms were differentially tested for identical exit status and identical output. --- .github/workflows/conote-upstream-sync.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/conote-upstream-sync.yml b/.github/workflows/conote-upstream-sync.yml index 713e97f11..f727fbfa7 100644 --- a/.github/workflows/conote-upstream-sync.yml +++ b/.github/workflows/conote-upstream-sync.yml @@ -102,7 +102,7 @@ jobs: # is deliberate so CoNote CI runs and a human approves before merge. git push origin "$BRANCH" - COMMITS=$(git log --oneline main..upstream/main | head -50) + COMMITS=$(git log --oneline main..upstream/main | awk 'NR<=50') gh pr create \ --base main \ --head "$BRANCH" \