@@ -30,7 +30,16 @@ WORKFLOW_FILE=.github/workflows/claude-pr-review.yml
3030OPEN=" \$ {$( printf ' %s' ' {' ) "
3131CLOSE=" $( 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
103124declared=$( 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.
127148if 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