jactionlint 2.5.0 adds jactionlint run, which runs the jobs of a workflow on your machine inside an OS sandbox, without a container engine. It also adds the unreachable-required-check rule, fixes two unpinned-images issues, and accepts the apm Dependabot ecosystem.
Added
jactionlint run: run workflows locally
jactionlint run lets you debug a run: script, an expression, a matrix or the needs wiring without pushing a commit. It shows what happened on your machine, not whether the workflow will pass on GitHub. Every skip or refusal is printed with its reason, and the run guide has a "Differences from GitHub" table. The workflow is first linted with the correctness profile, and the run stops on an error (--no-lint skips the lint). (#316, #317, #319, #320, #321, #322, #323, #324, @jdx)
jactionlint run --list # jobs and matrix cells in dependency order, nothing runs
jactionlint run ci -j test # run job `test` of .github/workflows/ci.yml and the jobs it needs
jactionlint run ci --dry-run # the steps with ${{ }} replaced, nothing runs
jactionlint run ci -e pull_request --secret TOKEN=abc --var REGION=eu
jactionlint run --check-sandbox # what this machine can enforce- Plan view.
--list(--jsonfor JSON) groups jobs into stages and expands matrices the way the runner does:exclude, theninclude. It evaluatesname:,runs-on:and jobif:expressions that it can, and marks each oneif is trueorif is false. Thegithubcontext comes from git,-i NAME=VALUEsetsworkflow_dispatchinputs, and--event-filesuppliesgithub.event. A matrix built fromneedsoutputs shows as "known at run time". - Steps and jobs. Supports
run:steps withshell,working-directory,defaults.run, layeredenv,timeout-minutesandcontinue-on-error. Also supported: step and job outputs,needs.*.result, matrices withfail-fast, background andparallel:steps, theGITHUB_ENV/PATH/OUTPUT/STATE/STEP_SUMMARYfile commands and workflow commands. Values from--secretand::add-mask::show as***in all output. Jobs whoseruns-onnames a different OS are skipped. Ctrl-C cancels the steps, and steps withif: always()still run. - Actions and reusable workflows. Runs local, composite and JavaScript actions (with
pre/post) and local or remote reusable workflows. If no Node.js onPATHhas the right major version, jactionlint downloads it and checks it against the release'sSHASUMS256.txt. Remote actions and reusable workflows must be pinned to a commit SHA. Use--trust-actionsto resolve a tag or branch and print the commit it picked.actions/checkout,upload-artifactanddownload-artifactare replaced with local versions. Use--no-shim ACTIONto run the real action. - Local actions cache. A cache service on
127.0.0.1works with unmodifiedactions/cache,setup-*,jdx/mise-actionand other actions built on@actions/cache, so a second run restores instead of downloading again. Caches are scoped to the ref and fall back to the default branch. Entries expire after 7 days, and the store has a 10 GB LRU limit (--cache-size). Related options:--cache=read-only|off,--no-cache,--cache-dir. Manage the store withjactionlint cache ls|rm|prune|path. - Network modes.
--net=openis the default.--net=noneblocks internet sockets.--net=proxyallows only a proxy that lets through GitHub and the common package registries. It needs Landlock ABI 4, which is Linux 6.7 or later.--allow-net HOST,...adds hosts, and--learnprints the hosts your steps contacted. - Containers (optional).
container:jobs,services:and Docker actions run throughdockerorpodman(--container-engine). By default all capabilities are dropped except nine, andno-new-privilegesis set. jactionlint never runs privileged containers or mounts the host home or Docker socket. Images must be pinned by digest unless you pass--trust-images.options:is checked against an allow list:--privileged,--cap-add,-v,--network hostand similar options are refused unless you pass--unsafe-container-options. Use--containerto run Linux matrix cells in a digest-pinnedubuntuimage, for example from a Mac. If no engine is installed, or you pass--no-containers, only the jobs and steps that need a container are skipped. - Parallel runs.
--parallel Nruns matrix cells and independent jobs at the same time. It respectsneedsandmax-parallel, and prints each job's output in one block. - Explanations. Messages that end with
(jactionlint run --explain ID)have a longer explanation.jactionlint run --explain listlists all of them.
Isolation and platform support: Each step runs with a filtered environment, a throwaway home, and a per-job copy of the workspace. Your checkout and real home cannot be read or written. Use --in-place to work in your checkout, --allow-read/--allow-write DIR to open more paths, and --sandbox=off to turn the sandbox off. If the sandbox can't be applied, the step does not run.
- Linux: Landlock and seccomp-bpf, Linux 5.13 or later.
- macOS: Seatbelt through
sandbox-exec. This backend is new and has had much less use than the Linux one. - Windows: steps are refused. Use WSL2.
New rule: unreachable-required-check
The new unreachable-required-check rule reports a required check that no workflow will report for a change. GitHub waits forever for such a check, and the pull request cannot merge. Common causes are a missing merge_group trigger, a branches filter that leaves out the protected branch, and a paths filter that skips a README-only change. (#331, @mariusvniekerk)
The rule turns on in every profile when required-checks is in the config file:
required-checks:
- name: CI gate
events: [pull_request, merge_group] # default: [pull_request]
branches: [main] # optional, literal namesIt reads every workflow in .github/workflows, so it doesn't report GitHub's workaround of a second workflow with the opposite paths filter. Each finding is placed where the fix goes: the config declaration (with a "did you mean" suggestion), on:, or the filter. When the answer depends on something the rule doesn't evaluate, it reports at level info and gives the reason. It does not read branch protection or rulesets from GitHub, so keep the list in sync with your repository settings. To turn the rule off, set rules: {unreachable-required-check: off}.
Fixed
unpinned-imagesnow treats images built by drop-in forks ofdocker/build-push-actionas local, the same as the original:useblacksmith/build-push-action,depot/build-push-actionandwarpbuilds/build-push-action. A laterdocker runof the image's tag is no longer reported. Other actions that take atags:input are still reported. (#328, @jdx, @tedserbinski)unpinned-imagesnow only treats an image as local when the same job builds, tags or loads it. Each job runs on its own runner, sodocker run app:ciin a job that doesn't buildapp:cipulls the image from the registry. That use is now reported. Previously, a build anywhere in the workflow hid the finding in every job. (#329, @jdx, @tedserbinski)dependabot-syntaxacceptspackage-ecosystem: apm(Agent Package Manager), which GitHub added on November 3, 2026. (#333, @frottke)
New Contributors
- @mariusvniekerk made their first contribution in #331
- @frottke made their first contribution in #333
- @tedserbinski made their first contribution in 4d0fcf6 (#328)
Full Changelog: v2.4.0...v2.5.0
💚 Sponsor jactionlint
jactionlint is a fork of rhysd/actionlint, maintained by @jdx, an open source developer at entire.io, the title sponsor of his open source work.
If jactionlint has a place in your development workflow, please consider becoming an individual or company sponsor. Your support funds ongoing development and helps keep jactionlint fast, free, and independent.