actionlint locally surfaced both bugs in 30 seconds.actionlint output:
.github/workflows/claude-release-watch.yml:114:0: could not parse as YAML: could not find expected ':' [syntax-check] | 114 | **Auto-filed by cc-release-watch** — see ...
The BODY=$(cat <<BODY ... BODY) heredoc body started at column 1.
YAML's run: | block scalar requires every line to be indented at
least as much as the block's base indentation (column 11 in this file).
YAML's parser sees the unindented heredoc line, decides the block ENDED,
and tries to parse **Auto-filed by ... as a top-level YAML
construct — fails. GHA accepts the file but silently fails to register
name: and workflow_dispatch:.
Fix: replace the heredoc with printf '%s\n'
with each line as a separate quoted argument. Every line stays indented
inside the YAML block, bash sees a multi-line string with embedded newlines.
BODY=$(printf '%s\n' \
"**Auto-filed by cc-release-watch** — see ..." \
"" \
"**Key:** \`${KEY}\`" \
...)
actionlint output:
.github/workflows/claude-release-watch.yml:55:17:
context "secrets" is not allowed here.
available contexts are "env", "github", "inputs", "job", "matrix", "needs",
"runner", "steps", "strategy", "vars". [expression]
| 55 | if: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN != '' }}
This was a bug I introduced in the M130 hotfix PR #1591.
I "fixed" the original env.CLAUDE_CODE_OAUTH_TOKEN by switching
to secrets.X, on the wrong belief that step-level env:
can't be read in step-level if:. Step-level env CAN'T, but the
secrets context isn't available in if: at all.
Fix: hoist CLAUDE_CODE_OAUTH_TOKEN to
job-level env:. Job-level env IS available in
step-level if:, so the original
if: ${{ env.CLAUDE_CODE_OAUTH_TOKEN != '' }} works correctly.
Bonus: the redundant step-level env: blocks for the same
secrets are now removed (DRY).
jobs:
watch:
env:
CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
...
- name: LLM-assisted feature extraction
if: ${{ env.CLAUDE_CODE_OAUTH_TOKEN != '' }}
run: node scripts/cc-triage.mjs
actionlint .github/workflows/*.yml on every workflow
change. It's installed (/usr/local/bin/actionlint) and
caught both bugs immediately..github/workflows/... CI but apparently
not enforcing on PRs to non-workflow files? — verify and harden.gh workflow list shows Claude Release Watch
as a friendly name (not file path).gh workflow run "Claude Release Watch" returns a run URL.