Uh oh!
There was an error while loading. Please reload this page.
fix: SIGPIPE under pipefail made the chart version guard skip a real chart change - #522
Merged
Merged
Conversation
…ange Three `producer | early-exiting-consumer` pipelines ran under `set -o pipefail`. When the consumer exits on its first match/line it closes the pipe, and once the producer has more than the ~64KB pipe buffer left to write it takes SIGPIPE and the pipeline exits 141 — a *success* case reported as failure. Measured on ubuntu-24.04 (bash 5.2.21 / GNU grep 3.11 / coreutils 9.4): 65,622 bytes is already enough. WHY this matters most in the chart guard: there the 141 lands on the `if !` branch, so a genuine `client/templates/**` edit is reported as "guard N/A" and the version-bump check is SKIPPED — the guard fails OPEN and waves through exactly the dark ship it was added to stop (PR #472 / the perIngestionTables flag block). Demonstrated end-to-end on a real repo: at 1,301 changed paths (58,723 bytes) with a template edit and NO version bump, the old body exits 0 "guard N/A"; the new one exits 1 and blocks. The mirror direction is broken too: a SIGPIPE on the `grep -qx client/Chart.yaml` MATCH short-circuits the `&&`, so a PR that DID bump the version is failed with a message that says it did not. Restructured rather than papered over with `|| true`, which would only convert a fail-open into a different fail-open: - chart-version-guard.yml — classify the changed-file list with a bash `read` loop + `case` (no pipe, no subprocess, so neither SIGPIPE nor a grep rc=2 can be mistaken for "no match"), and fail CLOSED with ::error:: on a missing base SHA or a failed `git diff`: "don't know" must never read as "nothing changed". - _pull_failure_detail — `head -n 3 <<< "$bad"`. With errexit live the old pipeline aborted the function AT that line, dropping the scoped pull event underneath it, i.e. the one actionable reason (x509 / blocked registry / auth). - _gpu_stack_signature — capture whole, take the first line with `%%$'\n'*`. This site was NOT reachable in practice (the trailing `|| true` already absorbed the 141), but that `|| true` swallowed every real failure code alike; `|| ..._out=""` states the actual contract (absent tool / timeout ⇒ empty ⇒ don't cache). Behaviour is unchanged below the buffer threshold: 669/669 bats tests pass, and _gpu_stack_signature is byte-identical across normal / multi-line / absent / timeout / 20k-line-chatty probe output. Verified on bash 3.2 (macOS floor) too. scripts/manifest.sha256 regenerated via scripts/gen-manifest.sh (R8). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LukasWodka
commented
Jul 31, 2026
ContributorAuthor
bugbot run |
LukasWodka
commented
Jul 31, 2026
ContributorAuthor
👋 Heads-up — Code review queue is at 33 / 30 Above the WIP limit. The team convention is to review existing PRs before opening new work. Open PRs currently in Code review (oldest first):
Pull from review before opening new work. (This is a nudge from the kanban WIP check, not a block.) |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit e023105. Configure here.
saadqbal
approved these changes
Jul 31, 2026
Uh oh!
There was an error while loading. Please reload this page.
LukasWodka added a commit
that referenced
this pull request
Jul 31, 2026
develop advanced between resolving the first merge and pushing it: #521 (macOS installs pinned kubectl/k3d/helm via the shared verified path) and #522 (SIGPIPE under pipefail made the chart guard skip a real chart change). Both touch scripts/, so the install-k8s.ps1 hash line in scripts/manifest.sha256 collided again — the fifth time today. Only the manifest conflicted. Neither PR touches scripts/lib/preflight.sh (they changed setup-macos.sh, gpu-nvidia.sh, install-client-helm.sh), so there is no semantic overlap with this branch's memory work; re-verified that _pf_total_mem_kb is still undefined and that no file carries conflict markers. Gates re-run on the merged tree, not carried over from the previous merge: bats scripts/tests/*.bats -> plan 692, ok 692, not ok 0 (complete TAP run; the suite grew from 683 because #521 added setup-macos.bats); shellcheck --severity=error over the CI file set -> rc=0; Pester -> 403 passed / 0 failed; check-style clean; check-drift no drift; gen-manifest.sh --check current. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LukasWodka
commented
Aug 2, 2026
ContributorAuthor
/fr-pass |
shujaatTracebloc pushed a commit
that referenced
this pull request
Aug 5, 2026
…able (#525) * fix(installer): make the absent-key path in _extract_yaml_value reachable `_extract_yaml_value` piped into `grep`. On an ABSENT key grep exits 1; under `set -o pipefail` that rc propagates out of the pipeline and out of the assignment, so under `set -e` the function aborts at the assignment — making the very next line, `[[ -z "$line" ]] && return`, unreachable in exactly the shape it exists to handle. Latent, not live: all three call sites (lines 209, 651, 652) use the `$( )` command-substitution form, which suspends errexit for the function body. But the documented contract is "empty when the key is absent", so a bare call is the natural next refactor — and it would abort the install mid-step. Fix is the house idiom already used in assess.sh and common.sh `_chart_version`: `|| line=""` on the assignment. Catching any non-zero also keeps the path reachable if `head -1` ever SIGPIPEs grep (141), the sibling shape fixed in #522. Contract written down above the function. Verified (bash 3.2.57, GNU grep): - bare call, absent key, errexit live -> before: exit 1 (aborts, `return` never runs) · after: exit 0, empty output, execution continues - `v="$(_extract_yaml_value …)"`, absent key -> exit 0, "" (unchanged) - found-key, quoting, and unreadable-file paths unchanged Adds a bats case pinning the BARE-statement call under `set -euo pipefail`. Mutation-tested: it fails against the unfixed function, so it cannot rot into a no-op. Regenerated scripts/manifest.sha256 (R8 gate). Fixes#523 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * ci: re-trigger — no workflow fired on the PR-open event (empty commit) Actions dispatched nothing for this PR: 0 runs on the branch 10 minutes after open, while a sibling PR opened 3 minutes later got all 7. Not a paths/types filter (standard-checks + chart-version-guard have no paths filter and also did not fire), not a draft, not an incident (status green), and PR head == remote head == local head. GitHub-side miss on the open event; `synchronize` re-dispatches all six gating workflows. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(installer): drop head from the pipeline — a duplicate key must keep its value (Bugbot, #525) The first fix (`grep | head -1 || line=""`) traded one failure for another: on a DUPLICATE key, head exits after the first line and SIGPIPEs grep (141); under pipefail the `|| line=""` fallback then wiped the successfully captured value, so detect_installed_client could miss a clientId and fail open toward overwrite. Capture every match and take the first line in the shell (`${line%%$'\n'*}`) — no downstream consumer, so grep's rc is 1 exactly when there is no match, which is the one case the fallback exists for. Regression test pins the duplicate-key bare-call shape; manifest regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Three
producer | early-exiting-consumerpipelines ran underset -o pipefail. When the consumer exits on its first match/line it closes the pipe; once the producer still has more than the ~64KB pipe buffer to write it takes SIGPIPE, and the pipeline exits 141 — a success case reported as a failure.Fixes two Bugbot findings from the #519 review. Restructured to remove the pipes rather than sprinkling
|| true, which would only trade one fail-open for another..github/workflows/chart-version-guard.ymlreadloop +case— no pipe, no subprocess. Plus explicit fail-closed::error::paths for a missing base SHA / failedgit diff.scripts/lib/install-client-helm.sh·_pull_failure_detailprintf … | head -n 3→head -n 3 <<< "$bad"scripts/lib/gpu-nvidia.sh·_gpu_stack_signature… | head -1 || true→ capture whole, first line via${var%%$'\n'*},|| var=""for the absent/timeout contractscripts/manifest.sha256regenerated withscripts/gen-manifest.sh(R8) — never hand-edited.Why finding 1 is the serious one: the guard failed OPEN
In the guard the 141 lands on an
if !branch, so a genuineclient/templates/**edit was reported as "guard N/A" and the version-bump check was skipped — waving through exactly the dark ship the gate was added to stop (PR #472 / theperIngestionTablesflag block).Evidence
Threshold — measured on the runner's own toolchain (
ubuntu:24.04, bash 5.2.21, GNU grep 3.11, coreutils 9.4)Pipeline exit status under
set -o pipefail, producer =printf, match on line 1:grep -q(match)head -n 3head -1Finding 1 — end-to-end, real
gitrepos, the workflow'srun:block extracted verbatim from the YAMLChart template changed, version NOT bumped ⇒ the gate must exit 1. Padding placed in
docs/so it sorts afterclient/templates/**(the match lands on line 1, with the rest of the stream still to write):OLD exit=0printedNo chart template/values change in this PR — guard N/A.— with a template edit sitting in the diff and no version bump.The mirror direction is broken too. A SIGPIPE on the
grep -qx 'client/Chart.yaml'match short-circuits the&&, so a PR that did bump the version gets failed with a message asserting it did not (client/Chart.yamlsorts first in byte order, so the match is on line 1):Gate semantics are otherwise preserved — all five control cases agree:
Finding 2 —
_pull_failure_detailThe real function, extracted from the lib,
set -euo pipefail, mockedkubectlemitting 20,000ImagePullBackOffpods (1,188,889 B). One process per shape so no outer||can suspend errexit:Isolating the inner pipeline confirms the mechanism in every redirection shape:
Finding 2 —
_gpu_stack_signature(behaviour-preserving hardening)Identical output old vs new across five probe shapes, including 20,000 lines (1.1 MB) of chatty
nvidia-ctkoutput:Gates
bats scripts/tests/*.bats— 669/669 pass, 0 failures (incl. the three_gpu_stack_signaturetests andset -e safe with no versions (#431 Bugbot))shellcheck --severity=warningon both touched libs — clean; the only hit is the pre-existing cross-fileSC2034 K3D_GPU_FLAGS, byte-identical ondevelopshellcheck --severity=erroron the full CI file set — cleanactionlint .github/workflows/chart-version-guard.yml— cleanbash -nboth libs — parsebash scripts/check-style.sh— cleanscripts/gen-manifest.sh --check— up to date (failed before regeneration, as expected)Two premises that did not hold up
"especially on large release-train diffs" overstates the reachability for this repo. The threshold is ~65 KB of changed paths (~900 files).
tracebloc/clienthas 175 tracked files total, 5,756 bytes of full path list — so even a diff touching every file is ~11× under the threshold, and the largest--name-onlylist in the last 400 merges tomainis 3,819 bytes. The fail-open is real and correctly diagnosed, but latent today, not an active dark-ship. It goes live ifclient/charts/ever gains vendored subcharts or the repo grows. Fixing it because a guard must fail closed by construction, not because the repo happens to be small._pull_failure_detaildoes not currently lose the x509 reason. The finding says the function "exits before emitting the scoped pull events". At the only call site —install-client-helm.sh:575,fail_detail="$(_pull_failure_detail "$ns" || true)"— the|| truesuspends errexit for the entire function body, so the 141 never aborts anything and the x509 line is still emitted (proven above:call=$(f || true)emits it even at 1.19 MB). The abort is only reachable from a bare-statement call. It also needs ~1,300+ pods inImagePullBackOffin one namespace. Worth fixing because returning 1 for "no pull failure" makes a bare call the natural next refactor — which would then abort the install mid-step with a bare141.Also spotted, deliberately not touched (out of scope)
scripts/lib/install-client-helm.sh:158,_extract_yaml_value:line=$(grep -E "^${key}:""$file"2>/dev/null | head -1)Same shape, but the latent hazard is a different one and unrelated to SIGPIPE: when the key is absent,
grepexits 1,pipefailmakes the pipeline 1, and the assignment returns 1 — yet line 159 ([[ -z "$line" ]] && return) shows "not found" is an expected path, so that line is unreachable in the shape that fails.Measured, same method as above:
All three live call sites (lines 209, 651, 652) use the
$( )form, so this is latent, not live — exactly the same masking as finding 2. Flagged for its own ticket rather than folded in here, since it needs a different fix (|| line="", not a herestring) and is outside both findings' scope.Notes
develop. Does not touch the open promotion PR release-train: develop -> staging #519, and no Bugbot review thread was resolved — a later release-train run picks this up.Test plan
bats scripts/tests/*.bats— 669/669shellcheck --severity=error(CI set) +--severity=warning(touched files)actionlinton the changed workflowbash scripts/check-style.shscripts/gen-manifest.sh --checkubuntu:24.04and bash 3.2🤖 Generated with Claude Code