Lint the bash inside the workflows, not only the bash in files #99

Merged
JMR-dev merged 2 commits from ci/actionlint into main 2026-08-25 15:13:01 +00:00
JMR-dev commented 2026-08-25 05:26:10 +00:00 (Migrated from github.com)

Closes #70.

The shellcheck step added earlier reads git ls-files '*.sh' — four files. It does not read inline run: blocks, and a good deal of this repo's bash lives there: release verification in build.yml, emulator setup and teardown in status_check.yml and api37-debug.yml. "shellcheck runs in CI" was true of the files and not of the blocks, and CLAUDE.md said so rather than pretending otherwise.

actionlint closes that half — it runs shellcheck over every run:, plus its own checks on expression syntax, needs: references, matrix keys and action inputs.

Pinned by digest, for two reasons

The first is the one shellcheck is pinned for: a new rule making untouched files fail is a red build whose diff can't explain it.

The second is specific to this tool — actionlint's documented install is bash <(curl -s .../download-actionlint.bash) off a moving branch. Running that in a repo that pins every action by SHA would contradict its own supply-chain posture more than the linter is worth. That's why #70 existed as a ticket instead of being bolted onto the shellcheck commit.

Its one finding is fixed, not suppressed

build.yml parsed ls to pick the release APK (SC2012). The glob was already in the line, so a bash array reads it without the pipe.

Proved it catches a plant

if [ $UNQUOTED = bad ]; then :; fi        ← planted in a build.yml run: block

shellcheck reported issue in this script: SC2086:info:4:6:

Removed again afterwards. A linter that can't be shown to catch a plant isn't wired in, it's just running — and SC2086 inside a run: block is invisible to the .sh-file step, which is the whole argument for this change.

CLAUDE.md loses the "does not cover inline run: blocks" caveat, because it no longer does. Both linters verified clean at their pinned digests.

Closes #70. The shellcheck step added earlier reads `git ls-files '*.sh'` — four files. It does **not** read inline `run:` blocks, and a good deal of this repo's bash lives there: release verification in `build.yml`, emulator setup and teardown in `status_check.yml` and `api37-debug.yml`. *"shellcheck runs in CI"* was true of the files and not of the blocks, and `CLAUDE.md` said so rather than pretending otherwise. `actionlint` closes that half — it runs shellcheck over every `run:`, plus its own checks on expression syntax, `needs:` references, matrix keys and action inputs. ## Pinned by digest, for two reasons The first is the one shellcheck is pinned for: a new rule making untouched files fail is a red build whose diff can't explain it. The second is specific to this tool — actionlint's documented install is `bash <(curl -s .../download-actionlint.bash)` **off a moving branch**. Running that in a repo that pins every action by SHA would contradict its own supply-chain posture more than the linter is worth. That's why #70 existed as a ticket instead of being bolted onto the shellcheck commit. ## Its one finding is fixed, not suppressed `build.yml` parsed `ls` to pick the release APK (**SC2012**). The glob was already in the line, so a bash array reads it without the pipe. ## Proved it catches a plant ``` if [ $UNQUOTED = bad ]; then :; fi ← planted in a build.yml run: block shellcheck reported issue in this script: SC2086:info:4:6: ``` Removed again afterwards. **A linter that can't be shown to catch a plant isn't wired in, it's just running** — and SC2086 inside a `run:` block is invisible to the `.sh`-file step, which is the whole argument for this change. `CLAUDE.md` loses the "does not cover inline `run:` blocks" caveat, because it no longer does. Both linters verified clean at their pinned digests.
Sign in to join this conversation.