fix(cnpg-drill): scan the manifest without a pipe, and name the real cause #154

Merged
binjovi-bot merged 1 commit from fix/drill-trap-no-pipe into trunk 2026-09-01 20:01:38 +00:00
Owner

The archive trap's "flake" is SIGPIPE, and it was never a transient.

printf '%s\n' "$body" | grep -qE "$TRAP_BACKUP_RE"

grep -q exits the instant it matches. The upstream printf is still writing, so it takes SIGPIPE and dies 141, and set -o pipefail reports the pipeline as 141 — for a successful match. if ...; then reads 141 as false, so the guard announced "no archive destination" precisely when it had found one.

Measured on this exact pattern — a 300k-line body matching on its first line:

match->0: 0   SIGPIPE->141: 40   other: 0

40 runs out of 40. Small manifests usually finish writing before grep exits, which is why this presented as a flake rather than a hole: the two gate runs that failed today each accepted one archiving manifest at a different case, because those cases sit near the buffer threshold where the race is a coin flip.

A herestring has no upstream process to signal, so 0 / 1 / 2+ stay distinct.

On the earlier fixes

#151 and #152 were not wrong, and #152 already made this safe: its status check turns 141 into a refusal. But it refused with "the scan did not complete" rather than naming the archive destination — and crucially, a test that only asserts the exit status passes with the pipe restored and pins nothing. I caught that when my first attempt at this test went green against the broken code. The new case asserts which FATAL is printed.

Red: with the pipe restored, the large archiving manifest is refused for the wrong reason.
Green: it is named as archiving. Suite 45s, shellcheck clean.

https://claude.ai/code/session_01LeVatedQ4sCA1u6LHjo7B5

**The archive trap's "flake" is SIGPIPE, and it was never a transient.** ```bash printf '%s\n' "$body" | grep -qE "$TRAP_BACKUP_RE" ``` `grep -q` exits the instant it matches. The upstream `printf` is still writing, so it takes SIGPIPE and dies **141**, and `set -o pipefail` reports the pipeline as 141 — **for a successful match**. `if ...; then` reads 141 as false, so the guard announced *"no archive destination"* precisely when it had found one. Measured on this exact pattern — a 300k-line body matching on its first line: ``` match->0: 0 SIGPIPE->141: 40 other: 0 ``` 40 runs out of 40. Small manifests usually finish writing before grep exits, which is why this presented as a flake rather than a hole: the two gate runs that failed today each accepted **one** archiving manifest at a **different** case, because those cases sit near the buffer threshold where the race is a coin flip. A herestring has no upstream process to signal, so 0 / 1 / 2+ stay distinct. ### On the earlier fixes #151 and #152 were not wrong, and #152 already made this **safe**: its status check turns 141 into a refusal. But it refused with *"the scan did not complete"* rather than naming the archive destination — and crucially, a test that only asserts the exit status **passes with the pipe restored and pins nothing**. I caught that when my first attempt at this test went green against the broken code. The new case asserts *which* FATAL is printed. **Red:** with the pipe restored, the large archiving manifest is refused for the wrong reason. **Green:** it is named as archiving. Suite 45s, shellcheck clean. https://claude.ai/code/session_01LeVatedQ4sCA1u6LHjo7B5
fix(cnpg-drill): scan the manifest without a pipe, and name the real cause
All checks were successful
binjovi/ci Binjovi completed the frozen plan
d4276c50c2
The archive trap's flake is not a transient. It is SIGPIPE.

    printf '%s\n' "$body" | grep -qE "$TRAP_BACKUP_RE"

grep -q exits the instant it matches. The upstream printf is still writing, so
it takes SIGPIPE and dies 141, and `set -o pipefail` reports the pipeline as
141 — for a SUCCESSFUL match. `if ...; then` reads 141 as false, so the guard
announced "no archive destination" precisely when it had found one.

Measured, on this exact pattern: a 300k-line body matching on its first line
returned 141 on 40 runs out of 40, and 0 on none of them. Small manifests
usually finish writing before grep exits, which is why it looked like a flake
rather than a hole: the two seanfarm gate runs that failed today each accepted
one archiving manifest at a DIFFERENT case, because those cases sit near the
buffer threshold where the race is a coin flip.

A herestring has no upstream process to signal, so 0/1/2+ stay distinct.

The previous commit's status check already turned this into a refusal, so the
trap was SAFE from that point on — but it refused with "the scan did not
complete" instead of naming the archive destination, and a test that only
checks the exit status passes with the pipe restored and pins nothing. The new
case asserts WHICH FATAL is printed.

Red: with the pipe restored, the large archiving manifest is refused for the
wrong reason. Green: it is named as archiving. Suite 45s, shellcheck clean.

Claude-Session: https://claude.ai/code/session_01LeVatedQ4sCA1u6LHjo7B5
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
sean/seanfarm!154
No description provided.