fix: lint .github/scripts with dotglob, and assert the sweep is total

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
This commit is contained in:
dan-claude-bot 2026-07-19 23:31:40 +00:00
parent 0f2ec1c893
commit 26ff38390b
2 changed files with 31 additions and 2 deletions

View file

@ -12,11 +12,22 @@ jobs:
# -x follows the `source=SCRIPTDIR/...` directives into commands/lib/. # -x follows the `source=SCRIPTDIR/...` directives into commands/lib/.
# globstar so a script in a new subdirectory is linted without anyone # globstar so a script in a new subdirectory is linted without anyone
# remembering to edit this list; bin/* covers the extensionless entrypoints. # 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: | run: |
shopt -s globstar shopt -s globstar dotglob
files=(bin/* **/*.sh) files=(bin/* **/*.sh)
printf 'shellcheck: %s\n' "${files[@]}" 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[@]}" shellcheck -x "${files[@]}"
- name: cli tests - name: cli tests
run: bash test/cli.sh run: bash test/cli.sh

View file

@ -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 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. 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 ## 0.2.0 — 2026-07-19
### Added ### Added