Repository navigation
ci: gate workflows on actionlint+shellcheck, plus self-test (BLO-29596) - #138
allyblockcast[bot] wants to merge 3 commits into
Conversation
BLO-29523 was a `bash -c 'cmd '$list` in github-ci.yml: the serial rerun handed pytest only the first failing file and turned the rest into positional parameters of the -c script, so three real failures were certified by one green check. shellcheck sees that shape. Nothing in CI was running shellcheck. .github/actionlint.yaml has been tracked since 7a91d45 and no job has ever consumed it; this adds the job. Measured at the parent commit with actionlint 1.7.7 + shellcheck 0.10.0: 18 findings across 4 workflows, all `shellcheck`, highest severity `warning`. Ten are fixed and eight suppressed with a written reason. No baseline file. - github-ci.yml: ADD_DOCKER_ENV becomes an array. It was deliberately left unquoted so "-e X=1" would split into two docker arguments, which is the same scalar-vs-array construct BLO-29523 died of one layer down. An array delivers those two arguments without re-splitting or globbing, and an empty array still expands to no arguments at all, which a quoted scalar could not do. The argv at all three call sites was checked byte-identical, empty and set, before and after (3x SC2086). - github-ci.yml: the two apt retry loops become `if cmd; then break; fi`. The old `cmd && break || sleep 10` needed its trailing `|| sleep 10` to keep `set -e` from killing the step on the first failed attempt, so the retry and the set -e suppression were the same token. Both the retry-then-succeed and the all-attempts-fail paths were replayed (2x SC2015, SC2034, 2x SC2086). - behind-base.yml: quote $GITHUB_OUTPUT. gtm-image-build.yml: one redirect for the three step outputs (SC2086, SC2129). - Suppressed: the `bash -lc` body whose `$` must be expanded by the shell inside the container, not the runner's; IMAGE, a workflow-level `env:` shellcheck cannot see from one run block; and six literal-markdown echoes in nbg6817-openwrt-ipk.yml, one of which carries a `\n` that is an argument to a remote tr. Each disable was confirmed load-bearing by deleting it and re-running. The job runs on arc-light in its own workflow rather than as a step in github-ci.yml, for the reason rerun-guard-selftest.yml already records: a check wired into the job it protects can abort that job before it reports anything, which recreates the silent hole the check exists to close. It also keeps a ~30 s linter off the ~2.5 h topotest critical path and out of that run's cancel-in-progress group. No -shellcheck severity floor is set, because none is needed: actionlint reports every shellcheck finding as its own error and exits 1 regardless of shellcheck's severity. The 18 findings above contained no `error`-severity finding and still exited 1, and so does the BLO-29523 shape, whose SC2128 is `warning` and SC2086 `info`. The self-test is the load-bearing part. actionlint with no shellcheck on PATH silently skips every SC* rule and exits 0 -- measured on this tree, the bug shape lints CLEAN that way. A green lint step is therefore ambiguous on its own, so the self-test resolves it on every run: the BLO-29523 shape must be rejected with SC2128, and the fixed shape must be accepted. Both arms are required, because without the second a checker that rejected everything would also pass arm one and be reported healthy. Signed-off-by: allyblockcast[bot] <allyblockcast[bot]@users.noreply.github.com> Co-Authored-By: Paperclip <noreply@paperclip.ing>
…523 bug shape at the rerun call site Deliberate red. Proves the actionlint gate rejects `bash -c 'cmd '$list` at the REAL call site in github-ci.yml, not only in the job's synthetic self-test fixture. Reverted by the next commit. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…shape" This reverts b007b0e, restoring the tree to c06276f exactly. Mutation evidence for BLO-29596 AC3 / the verifying signal is now complete: green c06276f actionlint run 38059664938 success RED b007b0e actionlint run 38070394475 failure, exit 1 .github/workflows/github-ci.yml:1021:9 SC2128:warning:340:55 Expanding an array without an index only gives the first element SC2086:info:340:55 Double quote to prevent globbing and word splitting The gate rejects the bug shape at the REAL rerun call site, not merely in the job's synthetic self-test fixture, so it is not vacuous. SC2128 is the near-exact diagnosis of the BLO-29523 defect, and actionlint exits 1 on it without any severity floor. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
@ally please review at head Review focus, in priority order:
Not asking you to re-derive the gate's non-vacuity: that is already proven end-to-end on this branch by green |
|
@ally please review at head Re-request: the first request (2026-10-11T01:16Z) is 13h old with zero reviews on either surface — Review focus:
|
Summary
Wires
actionlint+shellcheckinto CI as a gating job, and clears the 18 pre-existing findings so it can land green.BLO-29523 was a
bash -c 'cmd '$listingithub-ci.yml: the serial rerun handed pytest only the first failing file and turned the rest into positional parameters of the-cscript, so three real failures were certified by one green check. shellcheck sees that shape. Nothing in CI was running shellcheck — and.github/actionlint.yamlhas been tracked since7a91d45cwith no job ever consuming it. That unconsumed config was the actual gap.The 18 findings: 10 fixed, 8 suppressed with a reason, 0 baselined
Measured at the parent commit with actionlint 1.7.7 + shellcheck 0.10.0 — all 18 are rule
shellcheck, highest severitywarning:github-ci.ymlbehind-base.ymlgtm-image-build.ymlnbg6817-openwrt-ipk.ymlThe two substantive fixes:
ADD_DOCKER_ENVbecomes an array (3× SC2086). It was deliberately left unquoted so"-e X=1"would word-split into two docker arguments — the same scalar-vs-array construct BLO-29523 died of, one layer down. An array delivers those two arguments without re-splitting or globbing, and an empty array still expands to no arguments, which a quoted scalar could not do (""would hand docker a stray empty argument). I checked the argv at all three call sites is byte-identical, in both the empty and the set case, before and after.if cmd; then break; fi(2× SC2015, SC2034, 2× SC2086). The oldcmd && break || sleep 10needed its trailing|| sleep 10to stopset -ekilling the step on the first failed attempt — so the retry and theset -esuppression were the same token. Both the retry-then-succeed and all-attempts-fail paths were replayed.The 8 suppressions are each a genuine false positive, co-located with its reason: the
bash -lcbody whose$must be expanded by the shell inside the container;IMAGE, a workflow-levelenv:shellcheck cannot see from onerun:block; and six literal-markdown echoes, one carrying a\nthat is an argument to a remotetr. Each disable was confirmed load-bearing by deleting it and re-running — 1+1+5+1 = 8, so none is dead config.No severity floor is needed — measured, not assumed
The issue anticipated needing a
-shellcheckseverity floor because SC2086 is onlyinfo. It isn't needed: actionlint reports every shellcheck finding as its own error and exits 1 regardless of shellcheck's severity. The 18 findings above contained zeroerror-severity findings and still exited 1; so does the BLO-29523 shape, whose SC2128 iswarningand SC2086info. Adding a floor would be machinery for a gap that does not exist.The self-test is the load-bearing part
actionlintwith no shellcheck on PATH silently skips everySC*rule and exits 0. Measured on this tree: with shellcheck removed, the bug shape lints clean and the lint step goes green. So a green lint step is ambiguous on its own.The
Self-teststep resolves it on every run, with two arms:All four guards were mutation-tested one at a time:
rcguard firesrcguard deleted, shellcheck off PATHIsolation
Own workflow on
arc-light, not a step ingithub-ci.yml— the reasonrerun-guard-selftest.ymlalready records: a check wired into the job it protects can abort that job before it reports anything, recreating the silent hole it exists to close. It also keeps a ~30 s linter off the ~2.5 h topotest critical path and out of that run'scancel-in-progressgroup.Tools are pinned by version and sha256. actionlint's digest is the one published in
actionlint_1.7.7_checksums.txt; shellcheck publishes no checksums file for v0.10.0, so its digest was observed at download and pinned — that stops a later substitution, it does not independently attest the bytes.Also verified locally
check-runner-labels.pypass · its 4 fixtures OK ·test_topotest_*.py172 tests OK ·test_verify_rerun_coverage.py40 tests OK (the last two cover thegithub-ci.ymlwiring this PR edits).Mutation evidence — the gate is not vacuous (BLO-29596 verifying signal)
A gate that is green is not thereby a gate. Proven on this branch by pushing the
BLO-29523 bug shape at the real rerun call site in
github-ci.yml(not in thejob's synthetic self-test fixture), then reverting it:
c06276f2b007b0e1bash -c 'cd ~/frr/tests/topotests ; sudo -E pytest '$rerun_filesdadbffbdc06276f2The red run failed for exactly the right reason, not incidentally:
SC2128is the near-exact diagnosis of the BLO-29523 defect — "only gives the firstelement" is the bug, restated. And it is a
warning,SC2086only aninfo: bothstill exit 1, which is the measurement behind "no severity floor is needed" above.
Two corroborating reds at the mutation head, both expected:
rerun-guard-selftest(38070394496) and
review-gate-selftest(38070394516) — the
pre-existing guard from #68 catches the same shape independently, so the two layers
agree rather than one covering for the other.
Reproduce in ~2 min
Related Issue
BLO-29596 — follow-up from Ally's review of #68. Closes the regression guard for BLO-29523.
Components
build (CI only — no daemon code touched)