hack/check-rd-presets.sh can report a preset as missing while it is present in the file. It fired on a PR that touches no RD schema and turned Unit & controller tests red.
What the log showed:
hack/check-rd-presets.sh: line 39: printf: write error: Broken pipe
FAIL: packages/system/nats-rd/cozyrds/nats.yaml resourcesPreset enum missing: u1.nano
u1.nano is in that enum, both on the branch and on main. Running the same script against the same tree locally prints All RD schemas carry the full 47-preset enum.
The script sets set -euo pipefail on line 7, and line 39 is the membership test:
if ! printf '%s\n' "$enums" | grep -Fqx -- "$want"; then
grep -q exits as soon as it matches and closes the read end of the pipe. If printf has not finished writing by then it takes SIGPIPE and exits 141. Under pipefail the pipeline reports 141 instead of grep's 0, the ! inverts that into true, and a preset that was found gets recorded as missing. Whether printf completes its write before grep leaves is down to scheduling, so the same tree passes on one run and fails on the next.
Reproducer, deterministic because the larger input makes the write outlive grep:
$ bash -c 'set -euo pipefail; big=$(seq 1 200000)
if ! printf "%s\n" "$big" | grep -Fqx -- "1"; then echo MISSING; fi'
MISSING
1 is in that list. The same pipeline under plain set -o pipefail returns 141.
Two shapes fix it and I verified both against the reproducer: drop the pipe with grep -Fqx -- "$want" <<< "$enums", or do the membership test in bash with no external process at all.
Worth naming separately: the failure message points the reader at make generate in a chart they never touched, and the schema there is already correct, so there is nothing to regenerate. The wrong diagnosis costs more than the red itself.
The same printf | grep -q shape appears in other scripts that set pipefail, but their input is a single line, where the race is much less likely. I have not measured those.
Correction to the first version of this issue, which claimed the race explains the CI failure. I went and measured it at the size this script actually handles, and it does not reproduce: 3000 runs, then 3000 more with the CPU saturated the way a parallel make saturates a runner, zero false results. The largest enum any RD file yields is 3009 bytes (seaweedfs), well inside the 64 KiB pipe buffer, so printf finishes its single write before grep can leave and there is no SIGPIPE for it to take. The reproducer above still holds, but it needs an input far larger than anything this script sees.
So the mechanism behind that CI failure is not established, and the first version of this text asserted it was. What is established: the construct really does return 141 under pipefail when a write outlives its reader; the CI log carries that exact error on that exact line; and the preset it reported missing is present in the file, on the branch and on main. Whatever made the write fail there, the pipeline converts it into a wrong answer about the schema instead of surfacing it as a broken read.
That is the part worth fixing regardless of which cause is found. Removing the pipe removes the path by which any write error becomes a false "missing" — and it removes it without anyone having to first explain the write error.
hack/check-rd-presets.shcan report a preset as missing while it is present in the file. It fired on a PR that touches no RD schema and turnedUnit & controller testsred.What the log showed:
u1.nanois in that enum, both on the branch and on main. Running the same script against the same tree locally printsAll RD schemas carry the full 47-preset enum.The script sets
set -euo pipefailon line 7, and line 39 is the membership test:grep -qexits as soon as it matches and closes the read end of the pipe. Ifprintfhas not finished writing by then it takes SIGPIPE and exits 141. Underpipefailthe pipeline reports 141 instead of grep's 0, the!inverts that into true, and a preset that was found gets recorded as missing. Whetherprintfcompletes its write before grep leaves is down to scheduling, so the same tree passes on one run and fails on the next.Reproducer, deterministic because the larger input makes the write outlive grep:
1is in that list. The same pipeline under plainset -o pipefailreturns 141.Two shapes fix it and I verified both against the reproducer: drop the pipe with
grep -Fqx -- "$want" <<< "$enums", or do the membership test in bash with no external process at all.Worth naming separately: the failure message points the reader at
make generatein a chart they never touched, and the schema there is already correct, so there is nothing to regenerate. The wrong diagnosis costs more than the red itself.The same
printf | grep -qshape appears in other scripts that setpipefail, but their input is a single line, where the race is much less likely. I have not measured those.Correction to the first version of this issue, which claimed the race explains the CI failure. I went and measured it at the size this script actually handles, and it does not reproduce: 3000 runs, then 3000 more with the CPU saturated the way a parallel
makesaturates a runner, zero false results. The largest enum any RD file yields is 3009 bytes (seaweedfs), well inside the 64 KiB pipe buffer, soprintffinishes its single write beforegrepcan leave and there is no SIGPIPE for it to take. The reproducer above still holds, but it needs an input far larger than anything this script sees.So the mechanism behind that CI failure is not established, and the first version of this text asserted it was. What is established: the construct really does return 141 under
pipefailwhen a write outlives its reader; the CI log carries that exact error on that exact line; and the preset it reported missing is present in the file, on the branch and on main. Whatever made the write fail there, the pipeline converts it into a wrong answer about the schema instead of surfacing it as a broken read.That is the part worth fixing regardless of which cause is found. Removing the pipe removes the path by which any write error becomes a false "missing" — and it removes it without anyone having to first explain the write error.