Pin shellcheck, because the unpinned one disagreed with the local run
The step added in the previous commit went red on its own PR, and the reason is the one
CLAUDE.md already gives for pinning ktlint, detekt and JaCoCo: "a new rule in a linter
makes files nobody touched stop passing, so CI goes red on a PR whose diff cannot explain
it." Here it was not even a new rule, just a different version of the same tool.
The runner's ambient shellcheck is 0.9.0. The container used to check locally was 0.11.0.
They disagree about how to report `on_signal`, which is installed as the INT and TERM trap
eleven lines below its declaration and so is never called by name:
0.11.0 SC2329, once, on the function declaration -- "never invoked"
0.9.0 SC2317, seven times, one per command in the body -- "appears to be unreachable"
The disable directive named SC2329, so 0.11.0 was silent and 0.9.0 reported seven findings.
Nothing about the script was wrong; the local check simply was not the check CI ran.
Two changes, because either alone still leaves a way to be surprised:
- CI runs shellcheck from an image pinned by digest, so an upgrade is a line in this
file that someone chose, not something that arrives on a Tuesday. The version is
still printed, so a finding out of nowhere can be tied to that line.
- The directive names SC2317 and SC2329 both, so a contributor whose distro ships 0.9.0
gets the same answer locally as CI gives. Verified against both images: clean under
0.9.0 and clean under 0.11.0.
CLAUDE.md now says to check with the pinned digest rather than with whatever is installed,
which is what would have caught this before the push.
This commit is contained in:
@@ -160,16 +160,27 @@ jobs:
|
||||
# entry point itself -- and nothing was checking it. `git ls-files` rather than a
|
||||
# fixed list, so a script added later is covered without editing this workflow.
|
||||
#
|
||||
# Full severity, `info` included. The two findings it raises today are answered
|
||||
# with targeted `disable` directives carrying their reason, the same way
|
||||
# Full severity, `info` included. The findings it raises today are answered with
|
||||
# targeted `disable` directives carrying their reason, the same way
|
||||
# config/detekt/detekt.yml carries only the rules this codebase legitimately
|
||||
# breaks. A blanket --severity=warning would have hidden them and the next real
|
||||
# one alike. shellcheck is preinstalled on the ubuntu runner image; the version is
|
||||
# printed so a finding that appears out of nowhere can be pinned to an upgrade.
|
||||
# one alike.
|
||||
#
|
||||
# PINNED BY DIGEST, for the reason CLAUDE.md already gives for pinning ktlint,
|
||||
# detekt and JaCoCo: a new rule in a linter makes files nobody touched stop
|
||||
# passing, so CI goes red on a PR whose diff cannot explain it. That is not
|
||||
# hypothetical here. The first cut of this step used the runner's ambient
|
||||
# shellcheck, which is 0.9.0, and 0.9.0 reports a trap handler as seven
|
||||
# unreachable commands (SC2317) where 0.11.0 reports it once on the declaration
|
||||
# (SC2329) -- same script, same directive, different answer, and a red build on
|
||||
# the PR that introduced the step. The version is printed so a finding that
|
||||
# appears out of nowhere can be tied to a bump of this line.
|
||||
- name: shellcheck
|
||||
env:
|
||||
SHELLCHECK: koalaman/shellcheck@sha256:61862eba1fcf09a484ebcc6feea46f1782532571a34ed51fedf90dd25f925a8d
|
||||
run: |
|
||||
shellcheck --version
|
||||
git ls-files -z '*.sh' | xargs -0 -r shellcheck
|
||||
docker run --rm "$SHELLCHECK" --version
|
||||
git ls-files -z '*.sh' | xargs -0 -r docker run --rm -v "$PWD:/mnt" "$SHELLCHECK"
|
||||
|
||||
# `!cancelled()` rather than a plain sequence: a shellcheck failure above must not
|
||||
# cost the ktlint/detekt/lint lists. Same reason this step passes --continue -- one
|
||||
|
||||
@@ -142,8 +142,16 @@ install for code that can never run — and on API 37 the full APK does not fit
|
||||
two findings that raises today are answered with targeted `disable` directives carrying their
|
||||
reason, exactly as `config/detekt/detekt.yml` carries only the rules this codebase legitimately
|
||||
breaks. Do not silence it with `--severity=warning` — that hides the next real finding too.
|
||||
Locally there is no shellcheck package installed; `podman run --rm -v "$PWD:/mnt:z"
|
||||
docker.io/koalaman/shellcheck:stable <files>` is what was used.
|
||||
**It is pinned by image digest, and joins ktlint/detekt/JaCoCo in the "Dependency versions"
|
||||
rule above** — for exactly the reason stated there, demonstrated the day it was added. The first
|
||||
cut used the runner's ambient shellcheck. That is **0.9.0**, while the container used to check
|
||||
locally was 0.11.0, and the two disagree about how to report a trap handler: 0.11.0 says
|
||||
`SC2329` once on the declaration, 0.9.0 says `SC2317` on each of seven lines in the body. Same
|
||||
script, same directive, one green and one red. Directives that must survive both name both codes.
|
||||
|
||||
Locally, use the same pin rather than whatever is installed:
|
||||
`podman run --rm -v "$PWD:/mnt:z" docker.io/koalaman/shellcheck@sha256:61862eba... <files>`
|
||||
(the digest is in `status_check.yml`; there is no shellcheck system package on this host).
|
||||
|
||||
**It does not cover inline `run:` blocks in the workflows**, and a good deal of this repo's bash
|
||||
lives there. `actionlint` does cover them — it runs shellcheck over each `run:` — and reports one
|
||||
|
||||
@@ -456,7 +456,10 @@ cleanup() {
|
||||
# bash runs a trap only between commands, so this starts when whatever was in the foreground
|
||||
# returns -- which for Ctrl-C is immediately, because the same interrupt reached that command
|
||||
# too. `kill -INT` aimed at this script alone waits for the foreground command to finish.
|
||||
# shellcheck disable=SC2329 # invoked indirectly -- installed as the INT and TERM trap a few lines below.
|
||||
# Invoked indirectly -- installed as the INT and TERM trap a few lines below. Both codes,
|
||||
# because shellcheck 0.9.0 reports this as unreachable commands (SC2317) and 0.11.0 as an
|
||||
# uninvoked function (SC2329); CI pins 0.11.0 but a local install may be either.
|
||||
# shellcheck disable=SC2317,SC2329
|
||||
on_signal() {
|
||||
echo
|
||||
echo "interrupted (SIG$1) -- stopping the emulator and removing the AVDs this run created"
|
||||
|
||||
Reference in New Issue
Block a user