Repository navigation
Lint GitHub Actions workflows on pull requests, and test the parsed Publish job - #597
Merged
Merged
Conversation
Add a "Lint workflows" job to the pull request workflow that runs actionlint 1.7.12 from the official rhysd/actionlint image, pinned by digest. The image bundles shellcheck and pyflakes, so `run:` scripts are checked too. Fix the two warnings it reports on master that #594 does not touch: quote the version substitution passed to `towncrier build --version` (SC2046) in push.yaml and in the Makefile `changelog` target, and rename the deprecated `skip_existing` input of pypa/gh-action-pypi-publish to `skip-existing`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
test_publish_workflow_tags_only_after_pypi_succeeds sliced push.yaml's text from " Publish:" to the end of the file. With a later job holding contents: write and Publish's own permissions deleted, the slice read the later job's block and the test passed. It now asserts on yaml.safe_load(...)["jobs"]["Publish"]: contents: write, a fetch-depth 0 checkout, the tag step after the PyPI step with no condition, no continue-on-error on either step, and no `|| true` on the tag script. Mutation cases check that each assertion rejects the mistake it names. Add .github/actionlint.yaml so Lint workflows passes before #594 lands: it ignores, in push.yaml only, the two errors in the Test job's docs deploy step that #594 fixes. Delete both entries in whichever PR fixes or removes that step. Document running actionlint locally in the README. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Accept `permissions: write-all`, which also grants contents: write, and fail with the named message (not AttributeError) on any other string. - Compare fetch-depth as a string: action inputs are strings, so 0 and "0" are the same input, and `false` no longer passes as 0. - Require the tag step to run exactly `bash .github/publish-git-tag.sh`, which also rejects `|| exit 0`, `set +e` and the like, not only `|| true`. - Say that fetch-depth 0 fetches every tag, not that only it does: checkout's fetch-tags input also fetches tags. - Pin the README's local actionlint command to the version CI runs, and mention pyflakes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the delta review of cbb88b5: - A condition on the PyPI step (for example `if: github.ref == 'refs/heads/main'` on a master-branch repo) skips the upload without failing the job, so the tag step still ran. The check now rejects it, with its own mutation case. - Test that the equivalent settings the check accepts, `write-all` and `fetch-depth: "0"`, pass. - Lint workflows runs with `contents: read`, and its checkout does not persist the token into the workspace the container mounts. - The README notes that actionlint must run from the repo root: the path-scoped ignores match paths relative to the working directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An `if: success()` on either step changes nothing; the check still rejects any `if:`, since most conditions would skip a step. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
|
Hand-off to the US + core hub.
Review records, the #591 resolution and the drafts are in |
Contributor
Author
|
US + core hub merge audit, core#597 at
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
.github/workflows/pr.yamlruns actionlint 1.7.12, the latest release (2026-03-30). It uses the officialrhysd/actionlintimage pinned by digestsha256:b1934ee5…; on Docker Hub, both the1.7.12andlatesttags resolve to that digest. The image bundles shellcheck and pyflakes, so actionlint also checksrun:scripts. The job runs withcontents: read, and its checkout doesn't keep the token (persist-credentials: false).push.yaml,versioningjob, "Build changelog" step: the$(python -c …)passed totowncrier build --versionis now quoted. The Makefilechangelogtarget had the same pattern and is quoted too.skip_existingis nowskip-existing. Inpypa/gh-action-pypi-publish@release/v1'saction.yml,skip-existingis the canonical input;skip_existingis a deprecated alias passed through asinputs.skip-existing || inputs.skip_existing, so behavior is unchanged.test_publish_workflow_tags_only_after_pypi_succeedsused to slice the file text from" Publish:\n"to the end. It now asserts onyaml.safe_load(PUSH_WORKFLOW.read_text())["jobs"]["Publish"]..github/actionlint.yamlignores two error messages, inpush.yamlonly. Both come from the Test job's "Deploy documentation" step, which Deploy the documentation site from its own release job #594 fixes.Background: #594 fixes a release step that was skipped on every release since 2024-09-26. It was guarded by
if: matrix.os == 'ubuntu-latest'in a job with no matrix, and actionlint reports exactly that (property "os" is not defined in object type {}).Why the two ignores
Without them, Lint workflows would fail on every PR until #594 merges, and #594 waits on a go/no-go about resuming the public docs site. The two ignored errors are the
matrix.oscondition and theGITHUB_TOKENinput, whichJamesIves/github-pages-deploy-action@v4does not define. actionlint matches ignores by message only, so these are as narrow as it allows: one file and two exact messages.The cost: until the entries are deleted, a new
matrix.<key>reference in a matrix-less job inpush.yamlwould also be ignored. I checked this by adding one to Publish; actionlint stayed clean.pr.yamland every other check are unaffected. #594's owntests/core/test_docs_deployment.pyasserts that no job in either workflow references an undefined matrix key, so once #594 lands that bug is covered even if the ignore lingers. The config comment says to delete both entries in whichever PR fixes or removes the step.The test rewrite
The parsed test asserts, on Publish only:
permissions.contents == "write"(orwrite-all)fetch-depthis0(compared as a string, since action inputs are strings).github/publish-git-tag.shlooks for an existing tag locally, and a checkout withfetch-depth: 0fetches every tag.if:on either step; nocontinue-on-erroron the PyPI stepif:that skips the upload (saygithub.ref == 'refs/heads/main'on thismasterrepo) still lets the tag through.continue-on-erroron the tag step; itsrunis exactlybash .github/publish-git-tag.sh|| true,|| exit 0,set +eand the like.Exactly one checkout, one PyPI and one tag step must exist, each with a message naming what's missing. The
if:andcontinue-on-errorchecks go beyond the brief: they close the remaining ways the tag could run without a successful upload.Eight parametrized mutation cases each break one property and assert that the check fails with that property's message, so no assertion is vacuous. Two more cases check that equivalent settings (
permissions: write-all,fetch-depth: "0") pass. The first case is the false pass from the review of #594: Publish'spermissionsdeleted and a later Docs job withcontents: write.That false pass, on the real file (master's
push.yamlwith Publish'spermissionsblock removed and a Docs job appended after Publish):Merge order
.github/actionlint.yaml, or a one-line follow-up does.tests/core/test_release_tagging.py, and only there, in both orders. Publish a release only after its tests pass #591 appends its test at the end of the file, right after the test this PR replaces. Whichever lands second keeps both tests: this PR's file, with Publish a release only after its tests pass #591's test appended. Publish a release only after its tests pass #591'simport yamlline is identical to this PR's. That combined tree (master + Publish a release only after its tests pass #591a093ae0b+ this PR) passes 16 tests, and actionlint finds nothing.Checks run
uvx --from actionlint-py==1.7.12.25 actionlint, shellcheck 0.11.0):SC2046:warning:3:33; the deprecated-input error). Amatrix.oscondition in apr.yamljob and aneeds:typo each fail.make changelog, and the push.yaml step script extracted from the parsed YAML): both bump to 3.32.21. TheCHANGELOG.mdandpyproject.tomlthey produce are byte-identical to master's unquoted command.pytest tests/core/test_release_tagging.py: 15 passed (Python 3.13). The edge cases the review raised:write-allandfetch-depth: "0"pass;read-all,fetch-depth: false,fetch-depth: 1,|| exit 0andset +eeach fail on their own message.uvx ruff format --check .: 339 files already formatted.ruff checkon the test file: clean.policyengine-usalone had 44 runs queued, with 2 in progress org-wide.Not run locally: the Docker image (Docker isn't running on the machine that made this change). The config should still apply in CI, for three reasons:
.github/workflowsand.git(project.go), then loads.github/actionlint.yamlfrom it (config.go).paths:glob against each file's path relative to the working directory (linter.go).cf8fb6a1started the container with--workdir /github/workspaceand reported.github/workflows/push.yaml:79:13, so the glob sees exactly.github/workflows/push.yaml.The first green Lint workflows run on this PR will confirm it.
Review
An independent Opus review on Subfleet (
20261006-172501-core597-review) ofd67d103freturned APPROVE WITH NITS. That reviewer had no shell, so it worked from source. The commit after it,cbb88b57, fixes all its nits:fetch-tagsalso fetches tags;fetch-depth: falsepassing as 0;write-allraising AttributeError;|| trueregex;Its last finding, deleting the stale ignores once #594 lands, is a merge step. It's covered above and in a comment on #594.
A second reviewer, an in-session Opus agent that ran every command above, reviewed
cbb88b57and returned APPROVE WITH NITS. It reproduced the false pass and traced thepaths:matching through actionlint's source and thecf8fb6a1CI log.b7c4977ffixes its nits:if:let the tag through;write-alland"0"had no passing test;The same reviewer approved
b7c4977f, and then approved9a30bd55, which changes one word of a comment.This PR combines two sessions started from the same follow-up. The first opened it with the lint job and the two fixes (
cf8fb6a1). The second added the test rewrite, the ignore config, the hardening and the README line (d67d103fonward).Documentation review
action.yml.axiom: n/a: CI workflow and test change, no policy change
🤖 Generated with Claude Code