From 26ff38390be44c9c6b03a7de24fd33046a58c840 Mon Sep 17 00:00:00 2001 From: dan-claude-bot Date: Sun, 19 Jul 2026 23:31:40 +0000 Subject: [PATCH] fix: lint .github/scripts with dotglob, and assert the sweep is total MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI's shellcheck step set `globstar` and globbed `bin/* **/*.sh`. Globs do not match dot-prefixed names without `dotglob`, so `**/` never descended into `.github/` and two tracked scripts were linted by nothing: `.github/scripts/labels-reconcile.sh` and `.github/scripts/release-lib.sh`. release-lib.sh is the one that matters: it holds `changelog_section`, which release.yml sources to build the published release body and which test/release.sh's `changelog_armed` guard calls to decide whether main is armed. The script that decides both what ships and whether the changelog is safe was the script CI never read. Measured rather than assumed: `dotglob` adds exactly those two files to rig's line and nothing else, and `**` descending into `.git/` matches no `.sh` on a checkout. Both files already pass `shellcheck -x`, so this closes a hole in the net rather than fixing a defect behind it. Paired with a class check — `comm` against `git ls-files '*.sh'` — that fails the step naming any tracked script outside the globbed set, so the gap cannot reopen quietly. It also covers an escape `dotglob` does not: `globstar` declines to traverse symlinked directories. Refs #70 --- .github/workflows/ci.yml | 15 +++++++++++++-- CHANGELOG.md | 18 ++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0731ca..3ecbcee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,11 +12,22 @@ jobs: # -x follows the `source=SCRIPTDIR/...` directives into commands/lib/. # globstar so a script in a new subdirectory is linted without anyone # remembering to edit this list; bin/* covers the extensionless entrypoints. - # The file list is printed so under-coverage shows up in the log. + # dotglob because globs skip dot-prefixed names: without it `**/` never + # descends into `.github/`, so `.github/scripts/*.sh` — release-lib.sh + # among them — was swept up by nothing (#70). It also makes `**` + # descend into `.git/`, which holds no tracked `.sh` on a checkout. + # The file list is printed so under-coverage shows up in the log, and + # the comm below turns under-coverage into a failure rather than a + # thing someone has to notice: every tracked `.sh` must be in the set. run: | - shopt -s globstar + shopt -s globstar dotglob files=(bin/* **/*.sh) printf 'shellcheck: %s\n' "${files[@]}" + uncovered=$(comm -23 <(git ls-files '*.sh' | sort) <(printf '%s\n' "${files[@]}" | sort)) + if [ -n "$uncovered" ]; then + printf 'tracked .sh files the glob does not lint:\n%s\n' "$uncovered" >&2 + exit 1 + fi shellcheck -x "${files[@]}" - name: cli tests run: bash test/cli.sh diff --git a/CHANGELOG.md b/CHANGELOG.md index b4a5fac..7beda3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -181,6 +181,24 @@ on the way to cutting its first release, and this file starts there. and retires it. It stayed a separate change because it reaches markers on live machines that guard root SSH, and so needed a compat read this rename did not. +### Fixed + +- **CI's shellcheck sweep now reaches `.github/scripts/`** (#70) — the step + ran `shopt -s globstar` and globbed `bin/* **/*.sh`, but globs skip + dot-prefixed names without `dotglob`, so `**/` never descended into + `.github/` and two tracked scripts were linted by nothing: + `labels-reconcile.sh` and `release-lib.sh`. The second is the one that + stings — it holds `changelog_section`, the extraction `release.yml` sources + to build the published release body and the same function `test/release.sh`'s + `changelog_armed` guard (#66) calls to decide whether main is armed. The + script deciding both what ships and whether the changelog is safe was the + script CI never read. Adding `dotglob` pulls in exactly those two files and + nothing else; both already pass, so this closes a hole in the net rather + than fixing a defect behind it. Paired with a class check that fails the + step when any tracked `.sh` falls outside the globbed set, so the gap + cannot reopen quietly — including via a symlinked directory, which + `globstar` declines to traverse. + ## 0.2.0 — 2026-07-19 ### Added