From 6dc91637039169188cc89345efe6c32c781a688a Mon Sep 17 00:00:00 2001 From: Jeremy Daer Date: Wed, 5 Aug 2026 17:35:24 -0700 Subject: [PATCH] Make the lockstep check catch stale agreement and .yaml workflows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two holes in what shipped, both the same shape as the drift the check was written for. It globbed *.yml only. Every workflow here is .yml today so nothing was skipped, but GitHub honours .yaml just as well, and a .yaml workflow running a linter this check never opened is exactly the invisible drift it exists to catch. Scan both, and fail on an empty scan rather than passing a check that opened no files. It enforced agreement, not currency. Setting all three workflows to v2.9.0 passes cleanly and reproduces the release failure that motivated the check in the first place. So: a floor, next to WORKFLOW_DIR, moved in the same commit as the pins it constrains. Compare with sort -V. As strings v2.9.0 sorts above v2.11.1, because 9 > 1, so a lexical test would wave through the precise version that broke the release tag — and [ -gt ] parses neither. aur-publish.yml's pkgrel guard carries the same note for the same reason. --- scripts/check-lint-lockstep.sh | 44 ++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/scripts/check-lint-lockstep.sh b/scripts/check-lint-lockstep.sh index 23fdbaa1..3f8fc1f7 100755 --- a/scripts/check-lint-lockstep.sh +++ b/scripts/check-lint-lockstep.sh @@ -11,14 +11,43 @@ # others. An unpinned step resolves to whatever the action defaults to, which # drifts on its own schedule and would rebuild exactly the release-only mismatch # this exists to prevent — so a missing pin is a failure, not a skip. +# +# Agreement alone is not enough either: three workflows uniformly pinned back at +# v2.9.0 would pass this check and reproduce the release failure that motivated +# it. Hence a floor as well as a lockstep. set -euo pipefail WORKFLOW_DIR=".github/workflows" +# The oldest golangci-lint every workflow may pin. Raising this is a deliberate +# act, not housekeeping: move it in the same commit that moves the workflow +# pins, so the floor and the pins never disagree in a merged tree. +MIN_VERSION="v2.11.1" + +# Compare with sort -V, never lexically or arithmetically. As strings v2.9.0 +# sorts *above* v2.11.1 — 9 > 1 — so a lexical test would wave through the exact +# version that failed the release tag. `[ -gt ]` cannot parse either one. +version_gte() { + printf '%s\n%s\n' "$2" "$1" | sort -V | head -1 | grep -qx -- "$2" +} + +# Both spellings: every workflow here is .yml today, but GitHub honours .yaml +# just as well, and a .yaml workflow that ran a linter this check never opened +# would be exactly the invisible drift it exists to catch. +shopt -s nullglob +workflows=("$WORKFLOW_DIR"/*.yml "$WORKFLOW_DIR"/*.yaml) +shopt -u nullglob + +if [[ "${#workflows[@]}" -eq 0 ]]; then + echo "FAIL: no workflow files found under $WORKFLOW_DIR" + echo "An empty scan is a broken check, not a clean tree." + exit 1 +fi + # One line per golangci-lint-action step: ":", or # ":UNPINNED" when the step declares no version. mapfile -t pins < <( - for f in "$WORKFLOW_DIR"/*.yml; do + for f in "${workflows[@]}"; do awk -v file="$f" ' function close_step() { if (in_step) { print file ":UNPINNED"; in_step = 0 } @@ -68,4 +97,15 @@ if [[ "$count" -ne 1 ]]; then exit 1 fi -echo "golangci-lint lockstep check passed (${#pins[@]} steps pinned at $versions)" +if ! version_gte "$versions" "$MIN_VERSION"; then + echo "FAIL: golangci-lint pinned at $versions, below the floor $MIN_VERSION:" + printf ' %s\n' "${pins[@]}" + echo "" + echo "Agreeing on a stale version is still stale. v2.9.0 in lockstep would" + echo "pass the check above and fail the release on the same gosec G115 false" + echo "positive it was written to prevent. Raise the pins, then raise" + echo "MIN_VERSION in $0 in the same commit." + exit 1 +fi + +echo "golangci-lint lockstep check passed (${#pins[@]} steps across ${#workflows[@]} workflows pinned at $versions, floor $MIN_VERSION)"