github jdx/jactionlint v2.5.0
v2.5.0: Run workflows locally with jactionlint run

4 hours ago

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 (--json for JSON) groups jobs into stages and expands matrices the way the runner does: exclude, then include. It evaluates name:, runs-on: and job if: expressions that it can, and marks each one if is true or if is false. The github context comes from git, -i NAME=VALUE sets workflow_dispatch inputs, and --event-file supplies github.event. A matrix built from needs outputs shows as "known at run time".
  • Steps and jobs. Supports run: steps with shell, working-directory, defaults.run, layered env, timeout-minutes and continue-on-error. Also supported: step and job outputs, needs.*.result, matrices with fail-fast, background and parallel: steps, the GITHUB_ENV/PATH/OUTPUT/STATE/STEP_SUMMARY file commands and workflow commands. Values from --secret and ::add-mask:: show as *** in all output. Jobs whose runs-on names a different OS are skipped. Ctrl-C cancels the steps, and steps with if: 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 on PATH has the right major version, jactionlint downloads it and checks it against the release's SHASUMS256.txt. Remote actions and reusable workflows must be pinned to a commit SHA. Use --trust-actions to resolve a tag or branch and print the commit it picked. actions/checkout, upload-artifact and download-artifact are replaced with local versions. Use --no-shim ACTION to run the real action.
  • Local actions cache. A cache service on 127.0.0.1 works with unmodified actions/cache, setup-*, jdx/mise-action and 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 with jactionlint cache ls|rm|prune|path.
  • Network modes. --net=open is the default. --net=none blocks internet sockets. --net=proxy allows 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 --learn prints the hosts your steps contacted.
  • Containers (optional). container: jobs, services: and Docker actions run through docker or podman (--container-engine). By default all capabilities are dropped except nine, and no-new-privileges is 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 host and similar options are refused unless you pass --unsafe-container-options. Use --container to run Linux matrix cells in a digest-pinned ubuntu image, 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 N runs matrix cells and independent jobs at the same time. It respects needs and max-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 list lists 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 names

It 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-images now treats images built by drop-in forks of docker/build-push-action as local, the same as the original: useblacksmith/build-push-action, depot/build-push-action and warpbuilds/build-push-action. A later docker run of the image's tag is no longer reported. Other actions that take a tags: input are still reported. (#328, @jdx, @tedserbinski)
  • unpinned-images now only treats an image as local when the same job builds, tags or loads it. Each job runs on its own runner, so docker run app:ci in a job that doesn't build app:ci pulls 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-syntax accepts package-ecosystem: apm (Agent Package Manager), which GitHub added on November 3, 2026. (#333, @frottke)

New Contributors

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.

Don't miss a new jactionlint release

NewReleases is sending notifications on new releases.