From 7dc7818c8d71a086389ba03f23a840b54399541a Mon Sep 17 00:00:00 2001 From: Kaj Kowalski Date: Tue, 1 Sep 2026 09:33:05 +0200 Subject: [PATCH 1/3] Add a CLI action for runners without a Docker daemon The Docker action needs a daemon, so ubuntu-slim cannot use it. The workaround the docs carried reconstructed the release asset name by hand and got it wrong on macOS, where the assets say darwin rather than macos, and on Windows, where they are zip rather than tar.gz. It also ran the binary without a problem matcher, so nothing appeared as an annotation. The new action installs actionlint with mise, which selects the asset for the runner itself, and ships the generated matcher so callers no longer copy actionlint-matcher.json into their own repository. "make matcher" now writes both copies of that generated file, and the existing up-to-date guard covers them. --- .github/workflows/ci.yaml | 9 +++++ .github/workflows/matcher.yaml | 6 +-- Makefile | 8 ++-- README.md | 20 +++------- action/cli/action.yml | 60 ++++++++++++++++++++++++++++++ action/cli/actionlint-matcher.json | 17 +++++++++ docs/usage.md | 42 +++++++++++---------- 7 files changed, 122 insertions(+), 40 deletions(-) create mode 100644 action/cli/action.yml create mode 100644 action/cli/actionlint-matcher.json diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 602e7b244..ae58699b0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -174,6 +174,15 @@ jobs: } - name: Test action image run: ./scripts/test-action.bash actionlint-action:test + action-cli: + name: CLI action on a daemon-less runner + runs-on: ubuntu-slim + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + with: { persist-credentials: false } + - uses: $/action/cli + with: { pyflakes: "false" } + pre-commit-shellcheck: name: pre-commit ShellCheck integration runs-on: ubuntu-latest diff --git a/.github/workflows/matcher.yaml b/.github/workflows/matcher.yaml index ba40c91d6..542af873d 100644 --- a/.github/workflows/matcher.yaml +++ b/.github/workflows/matcher.yaml @@ -27,13 +27,13 @@ jobs: run: make ./scripts/generate-actionlint-matcher/testdata/* SKIP_GO_GENERATE=true - name: Test actionlint-matcher.json run: npm run test:matcher - - name: Ensure .github/actionlint-matcher.json is up-to-date + - name: Ensure the generated matcher files are up-to-date run: | - make .github/actionlint-matcher.json + make matcher if git diff --quiet; then echo 'OK' else - echo 'ERROR! .github/actionlint-matcher.json is outdated. Update it by "make .github/actionlint-matcher.json"' >&2 + echo 'ERROR! A generated actionlint-matcher.json is outdated. Update both by "make matcher"' >&2 set -x git diff exit 1 diff --git a/Makefile b/Makefile index 5413d18d6..4c1134308 100644 --- a/Makefile +++ b/Makefile @@ -110,8 +110,10 @@ man: man/actionlint.1 man/actionlint.1.html bench: go test -bench Lint -benchmem -.github/actionlint-matcher.json: scripts/generate-actionlint-matcher/object.mjs - node ./scripts/generate-actionlint-matcher/main.mjs .github/actionlint-matcher.json +.github/actionlint-matcher.json action/cli/actionlint-matcher.json: scripts/generate-actionlint-matcher/object.mjs + node ./scripts/generate-actionlint-matcher/main.mjs $@ + +matcher: .github/actionlint-matcher.json action/cli/actionlint-matcher.json scripts/generate-actionlint-matcher/testdata/escape.txt: $(TARGET) ./actionlint -color ./testdata/err/one_error.yaml > ./scripts/generate-actionlint-matcher/testdata/escape.txt || true @@ -126,4 +128,4 @@ CHANGELOG.md: c clean: rm -f ./$(TARGET) ./man/actionlint.1 ./man/actionlint.1.html ./actionlint-workflow-ast -.PHONY: all test clean build lint fuzz man bench cov b t c l CHANGELOG.md FORCE +.PHONY: all test clean build lint fuzz man matcher bench cov b t c l CHANGELOG.md FORCE diff --git a/README.md b/README.md index 5503832fa..76b7f354c 100644 --- a/README.md +++ b/README.md @@ -135,25 +135,15 @@ jobs: - uses: kjanat/actionlint@v1 ``` -On a daemon-less runner such as `ubuntu-slim`, download and run the binary instead: +On a daemon-less runner such as `ubuntu-slim`, use the CLI action instead. It installs actionlint with [mise](https://mise.jdx.dev) and registers the problem matcher, so errors still show up as annotations: ```yaml -- uses: actions/checkout@v7 - with: { persist-credentials: false } -- name: Download and run actionlint - env: { GH_TOKEN: "${{ github.token }}", GH_REPO: "kjanat/actionlint" } - run: | - case "${RUNNER_ARCH}" in - X64) asset_arch=amd64 ;; - ARM64) asset_arch=arm64 ;; - ARM) asset_arch=armv6 ;; - X86) asset_arch=386 ;; - *) echo "Unsupported runner architecture: ${RUNNER_ARCH}" >&2; exit 1 ;; - esac - gh release download --pattern "actionlint_*_${RUNNER_OS,,}_${asset_arch}.tar.gz" --output - | tar -xzf - actionlint - ./actionlint -color +- { uses: actions/checkout@v7, with: { persist-credentials: false } } +- uses: kjanat/actionlint/action/cli@v1 ``` +ShellCheck and pyflakes are installed alongside actionlint unless you set `shellcheck: "false"` and `pyflakes: "false"`. + The moving `v1` tag follows compatible v1 releases. `v1.13.0` is a versioned release tag, but only a full-length commit SHA provides an immutable action reference.

Inputs

diff --git a/action/cli/action.yml b/action/cli/action.yml new file mode 100644 index 000000000..c004b58f8 --- /dev/null +++ b/action/cli/action.yml @@ -0,0 +1,60 @@ +# yaml-language-server: $schema=https://www.schemastore.org/github-action.json +--- +name: actionlint CLI by @kjanat +description: >- + Run actionlint on GitHub Actions workflow files and report problems as + annotations. Installs actionlint with mise, so no Docker daemon is needed. +inputs: + version: + description: >- + actionlint release to install, for example 1.13.0. "latest" takes the + newest release. + required: false + default: latest + shellcheck: + description: >- + Install ShellCheck so actionlint checks shell scripts in "run" steps. + required: false + default: "true" + pyflakes: + description: >- + Install pyflakes so actionlint checks Python scripts in "run" steps. + required: false + default: "true" + working-directory: + description: Directory to run actionlint in, relative to the workspace. + required: false + default: . + flags: + description: >- + Extra actionlint flags, for example "-ignore SC2086". Split on whitespace, + so it is for workflow authors rather than untrusted input. + required: false + default: "" +runs: + using: composite + steps: + - uses: jdx/mise-action@c2a87611a18de5b3828c5652fe268e992400cb5c # v4.3.0 + with: + mise_toml: | + [tools] + "github:kjanat/actionlint" = "${{ inputs.version }}" + ${{ inputs.shellcheck == 'true' && 'shellcheck = "latest"' || '' }} + ${{ inputs.pyflakes == 'true' && '"pipx:pyflakes" = "latest"' || '' }} + + [settings] + minimum_release_age = "0s" + + - shell: bash + env: + WORKING_DIRECTORY: ${{ inputs.working-directory }} + FLAGS: ${{ inputs.flags }} + run: | + set -euo pipefail + echo "::add-matcher::${GITHUB_ACTION_PATH}/actionlint-matcher.json" + read -ra flags <<< "${FLAGS}" + cd "${WORKING_DIRECTORY}" + actionlint -color "${flags[@]}" +branding: + icon: check-circle + color: blue diff --git a/action/cli/actionlint-matcher.json b/action/cli/actionlint-matcher.json new file mode 100644 index 000000000..4613e1617 --- /dev/null +++ b/action/cli/actionlint-matcher.json @@ -0,0 +1,17 @@ +{ + "problemMatcher": [ + { + "owner": "actionlint", + "pattern": [ + { + "regexp": "^(?:\\x1b\\[\\d+m)?(.+?)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*:(?:\\x1b\\[\\d+m)*(\\d+)(?:\\x1b\\[\\d+m)*: (?:\\x1b\\[\\d+m)*(.+?)(?:\\x1b\\[\\d+m)* \\[(.+?)\\]$", + "file": 1, + "line": 2, + "column": 3, + "message": 4, + "code": 5 + } + ] + } + ] +} diff --git a/docs/usage.md b/docs/usage.md index 6087211c4..b99876c76 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -303,31 +303,34 @@ socket. Standard Ubuntu runners, including `ubuntu-24.04-arm` and `ubuntu-26.04-arm`, are supported by the published `linux/amd64` and `linux/arm64` image. -On a daemon-less runner such as `ubuntu-slim`, download and run the binary -instead. `uses: docker://ghcr.io/kjanat/actionlint:latest` is not an alternative -there because it has the same Docker daemon requirement. +On a daemon-less runner such as `ubuntu-slim`, use the CLI action at +`action/cli` instead. `uses: docker://ghcr.io/kjanat/actionlint:latest` is not an +alternative there because it has the same Docker daemon requirement. ```yaml - uses: actions/checkout@v7 with: { persist-credentials: false } -- name: Download and run actionlint - env: { GH_TOKEN: "${{ github.token }}", GH_REPO: "kjanat/actionlint" } - run: | - case "${RUNNER_ARCH}" in - X64) asset_arch=amd64 ;; - ARM64) asset_arch=arm64 ;; - ARM) asset_arch=armv6 ;; - X86) asset_arch=386 ;; - *) echo "Unsupported runner architecture: ${RUNNER_ARCH}" >&2; exit 1 ;; - esac - gh release download --pattern "actionlint_*_${RUNNER_OS,,}_${asset_arch}.tar.gz" --output - | tar -xzf - actionlint - ./actionlint -color +- uses: kjanat/actionlint/action/cli@v1 +``` + +It installs actionlint with [mise][mise], which picks the release asset for the +runner's platform, and registers the [problem matcher](#problem-matchers) so +errors appear as annotations. No Docker daemon and no copied matcher file are +needed. + +ShellCheck and pyflakes are installed alongside actionlint unless you turn them +off: + +```yaml +- uses: kjanat/actionlint/action/cli@v1 + with: { shellcheck: "false", pyflakes: "false" } ``` -The binary-only path does not bundle ShellCheck or pyflakes; install them on the -runner when those integrations are required. `v1` moves to each new release. -`v1.13.0` is a versioned release tag, but only a full-length commit SHA provides -an immutable action reference. +The other inputs are `version` for the actionlint release, `working-directory`, +and `flags` for extra command line options such as `-ignore`. `v1` moves to each +new release. `v1.13.0` is a +versioned release tag, but only a full-length commit SHA provides an immutable +action reference. The action accepts these inputs: @@ -785,6 +788,7 @@ You can also see actionlint issues inline in VS Code via the [Trunk VS Code exte [go-shellcheck]: https://github.com/wasilibs/go-shellcheck [go-template]: https://pkg.go.dev/text/template [jsonl]: https://jsonlines.org/ +[mise]: https://mise.jdx.dev [nova-extension]: https://extensions.panic.com/extensions/org.netwrk/org.netwrk.actionlint/ [nova]: https://nova.app [nvim-lint]: https://github.com/mfussenegger/nvim-lint From 340d3b9f279daf3ac80ff85f382377c08998af61 Mon Sep 17 00:00:00 2001 From: Kaj Kowalski Date: Tue, 1 Sep 2026 10:14:03 +0200 Subject: [PATCH 2/3] Keep the versioned release tag note on one line Rewriting the daemon-less section split the sentence across two lines, so the bump-version rule that matches it found no occurrence and TestDeclaredTargetsMatchRepository failed. --- docs/usage.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/usage.md b/docs/usage.md index b99876c76..45d9691d4 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -328,9 +328,9 @@ off: The other inputs are `version` for the actionlint release, `working-directory`, and `flags` for extra command line options such as `-ignore`. `v1` moves to each -new release. `v1.13.0` is a -versioned release tag, but only a full-length commit SHA provides an immutable -action reference. +new release. +`v1.13.0` is a versioned release tag, but only a full-length commit SHA provides +an immutable action reference. The action accepts these inputs: From 62bf65b7945f49494f4f65ffceaf484f232d5f64 Mon Sep 17 00:00:00 2001 From: Kaj Kowalski Date: Tue, 1 Sep 2026 16:12:09 +0200 Subject: [PATCH 3/3] Lint a fixture in the CLI action smoke test The action installs the published actionlint, which is older than this branch, so pointing it at the repository reported the self-repository "uses:" syntax, "concurrency.queue" and the parallel step keywords as errors. Those are features the release does not carry yet. A fixture workflow written to the runner's temp directory keeps the job about what it is meant to prove: that the action installs actionlint on a runner without a Docker daemon and runs it. --- .github/workflows/ci.yaml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ae58699b0..193275a19 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -180,8 +180,22 @@ jobs: steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 with: { persist-credentials: false } + # The published actionlint the action installs is older than this branch, + # so linting the repository itself would report syntax this release does + # not know yet. A fixture keeps the job about the action. + - name: Write a fixture workflow + run: | + mkdir -p "${RUNNER_TEMP}/fixture" + cat > "${RUNNER_TEMP}/fixture/smoke.yaml" <<'FIXTURE' + on: push + jobs: + smoke: + runs-on: ubuntu-latest + steps: + - run: echo hello + FIXTURE - uses: $/action/cli - with: { pyflakes: "false" } + with: { pyflakes: "false", flags: "${{ runner.temp }}/fixture/smoke.yaml" } pre-commit-shellcheck: name: pre-commit ShellCheck integration