Skip to content

Commit 492edec

Browse files
committed
test(review): run actionlint in CI and stop failing open
1 parent f36e7a6 commit 492edec

3 files changed

Lines changed: 47 additions & 6 deletions

File tree

‎.github/workflows/tests.yml‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,26 @@ jobs:
1616
with:
1717
fetch-depth: 1
1818

19+
# actionlint is the check that would have caught the outage, so it has to actually run
20+
# here: it is not on the runner image, and without this step the lint test reports
21+
# "skip actionlint not installed" forever and only its own awk backstop runs.
22+
#
23+
# Downloaded and checksummed rather than pulled in as a third-party action, because
24+
# this repository's workflows are what gate every merge in the org -- an unpinned
25+
# `uses:` here would be a supply-chain hole in the one place it hurts most.
26+
- name: Install actionlint
27+
env:
28+
ACTIONLINT_VERSION: 1.7.12
29+
ACTIONLINT_SHA256: 8aca8db96f1b94770f1b0d72b6dddcb1ebb8123cb3712530b08cc387b349a3d8
30+
run: |
31+
curl -fsSL -o actionlint.tar.gz \
32+
"https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}/actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz"
33+
echo "${ACTIONLINT_SHA256} actionlint.tar.gz" | sha256sum --check --strict
34+
tar -xzf actionlint.tar.gz actionlint
35+
sudo install -m 0755 actionlint /usr/local/bin/actionlint
36+
rm -f actionlint actionlint.tar.gz
37+
actionlint --version
38+
1939
# First, because it is the check whose absence took the org down: a workflow that
2040
# Actions cannot parse never starts, so the required check never reports and every pull
2141
# request in the org blocks. Nothing else in this suite can see that -- the file is

‎tests/context-step-test.sh‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ extract_step() {
3636
' "$WORKFLOW"
3737
}
3838

39-
# Assembled rather than written literally: this file must not contain the delimiter either,
40-
# or the grep below finds itself when someone greps the test suite for it.
39+
# One definition, shared by the two sed patterns and the grep below, so the thing being
40+
# substituted and the thing being forbidden cannot drift apart.
4141
EXPR_OPEN="\${$(printf '%s' '{')"
4242
extract_step \
4343
| sed -e "s/${EXPR_OPEN} github.event.pull_request.number }}/\"\$STUB_PR\"/g" \

‎tests/workflow-lint-test.sh‎

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,16 @@ WORKFLOW_FILE=.github/workflows/claude-pr-review.yml
3030
OPEN="\${$(printf '%s' '{')"
3131
CLOSE="$(printf '%s' '}')}"
3232

33-
for wf in .github/workflows/*.yml; do
33+
# Actions loads .yaml as well as .yml, and an unscanned workflow would go unmentioned
34+
# rather than reported.
35+
shopt -s nullglob
36+
WORKFLOWS=(.github/workflows/*.yml .github/workflows/*.yaml)
37+
if [ "${#WORKFLOWS[@]}" -eq 0 ]; then
38+
echo "FAIL no workflow files found; the scan proves nothing"
39+
exit 1
40+
fi
41+
42+
for wf in "${WORKFLOWS[@]}"; do
3443
# The distinction that caused the outage, and the one the scan has to make: a delimiter in
3544
# a *YAML* comment is stripped by the YAML parser and never reaches Actions, while the same
3645
# characters inside a block scalar are part of the string value and are parsed. So block
@@ -46,11 +55,14 @@ for wf in .github/workflows/*.yml; do
4655
if (j == 0) { print FILENAME ":" FNR ": unterminated expression (" where ")"; return }
4756
expr = substr(rest, 1, j - 1)
4857
gsub(/^[ \t]+|[ \t]+$/, "", expr)
58+
# Empty and unterminated only. A character class over what an expression may contain
59+
# rejects valid ones -- hashFiles and format calls use slashes, braces and percent
60+
# signs that no reasonable class covers -- and a heuristic that hard-fails CI on
61+
# correct input gets deleted rather than fixed. actionlint checks the grammar
62+
# properly, and now actually runs in CI.
4963
if (expr == "")
5064
print FILENAME ":" FNR ": empty expression in " where \
5165
" -- Actions rejects the whole workflow"
52-
else if (expr !~ /^[A-Za-z_0-9.,()!<>=&|*'\''"\[\] \t-]+$/)
53-
print FILENAME ":" FNR ": odd expression in " where ": " expr
5466
line = substr(rest, j + length(shut))
5567
}
5668
}
@@ -100,6 +112,15 @@ step_script=$(awk '
100112
in_run && /^ [a-z]/ { exit }
101113
in_run { print }
102114
' "$WORKFLOW_FILE")
115+
# Fail loudly if the extraction drifted. check_permission returns early when the pattern is
116+
# absent from the script, so an empty step_script silently turns all six checks into no-ops --
117+
# in the one file whose purpose is catching a permission that is silently missing. (declared
118+
# fails safe: empty means every check reports FAIL.)
119+
if [ "$(printf '%s\n' "$step_script" | wc -l)" -lt 100 ]; then
120+
echo "FAIL the context-step extraction no longer matches $WORKFLOW_FILE;" \
121+
"the permission table proves nothing"
122+
failures=$((failures + 1))
123+
fi
103124
declared=$(awk '/^ permissions:$/ { p = 1; next } p && /^ [a-z-]+:/ { print $1 } p && /^ [a-z]/ { exit }' \
104125
"$WORKFLOW_FILE" | tr -d ':')
105126

@@ -125,7 +146,7 @@ check_permission "/pulls/" pull-requests "the PR reads"
125146
# grammar, and the shell; run it when it is on PATH. shellcheck findings are excluded because
126147
# the run blocks here intentionally use unquoted word splitting for job ids.
127148
if command -v actionlint >/dev/null 2>&1; then
128-
if out=$(actionlint .github/workflows/*.yml 2>&1); then
149+
if out=$(actionlint "${WORKFLOWS[@]}" 2>&1); then
129150
echo "ok actionlint reports no findings"
130151
else
131152
remaining=$(printf '%s\n' "$out" | grep -v 'shellcheck reported' || true)

0 commit comments

Comments
 (0)