From a3d814b945a318beff1c03686163ac2f9d9f192a Mon Sep 17 00:00:00 2001 From: k2kite-megankim Date: Sun, 3 May 2026 23:28:14 +0900 Subject: [PATCH 1/4] feat(ci): add actionlint workflow + self-hosted runner whitelist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Catches GitHub Actions expression / shell / yaml errors at PR review time instead of post-merge tag-push. Today's marketplace-publish.yml regression — an `\${{ }}` literal embedded in a shell `#` comment that GitHub evaluated as an empty expression — failed silently at workflow-load (referenced_workflows: [], 0 jobs ran) and took 4 canary attempts to diagnose. actionlint caught the issue locally with a precise file:line:col error pointing at the comment. This workflow: - Runs on every PR + every push to main - Lints all 4 workflow files this repo owns (marketplace-publish, plugin-ci, validate-plugin-manifest, actionlint itself) - Uses upstream rhysd/actionlint installer pinned to v1.7.12 - shellcheck bundled by default — also catches `run:` block issues `.github/actionlint.yaml` whitelists the `oracle` self-hosted runner label so the linter doesn't false-positive flag it (the rest of our labels — `self-hosted`, `linux`, `arm64` — are already known to actionlint). Verified: locally ran `./actionlint -no-color` against all 4 workflow files post-config — clean. Closes lvis-project/.github P3 follow-up from the reusable-workflow-rollout retro. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/actionlint.yaml | 10 +++++++ .github/workflows/actionlint.yml | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 .github/actionlint.yaml create mode 100644 .github/workflows/actionlint.yml diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 0000000..8398d6e --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,10 @@ +# actionlint config +# +# Whitelist self-hosted runner labels used by the org's runners. +# Without this, actionlint reports "label 'oracle' is unknown" — a +# false positive for our `[self-hosted, linux, arm64, oracle]` +# runner pool. The other labels (self-hosted, linux, arm64) are +# already known to actionlint. +self-hosted-runner: + labels: + - oracle diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml new file mode 100644 index 0000000..8bc198b --- /dev/null +++ b/.github/workflows/actionlint.yml @@ -0,0 +1,46 @@ +name: actionlint +# +# Lints all workflow files in this repo on every PR + push to main. +# +# Why: today's marketplace-publish.yml had an `${{ }}` literal inside +# a shell `#` comment that GitHub Actions evaluated as an empty +# expression, failing the workflow at load-time with +# `referenced_workflows: []` — a frustrating "0 jobs ran" failure +# mode that took 4 canary attempts to diagnose. `actionlint`'s +# expression parser catches this class of bug at PR review time +# instead of post-merge tag-push. +# +# Coverage: `marketplace-publish.yml`, `plugin-ci.yml`, and +# `validate-plugin-manifest.yml` — the three reusable workflows +# this repo owns. +# +# Self-hosted runner labels (`oracle`, ...) are whitelisted +# in `.github/actionlint.yaml` so the linter doesn't flag them as +# unknown. + +on: + pull_request: + push: + branches: [main] + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install actionlint + # Use the upstream installer script (rhysd/actionlint owner- + # maintained) rather than a third-party wrapper action — keeps + # the supply chain to one source of truth + a known-version + # binary. Pinned via the script's first arg. + run: | + curl -sL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash \ + | bash -s -- 1.7.12 + ./actionlint -version + + - name: Run actionlint + # `-color=never` for cleaner CI logs. + # actionlint auto-discovers `.github/actionlint.yaml` for + # self-hosted runner label whitelisting. + run: ./actionlint -color=never From 6990a8f67d7f0af944d3432b67ddc62ea9120b04 Mon Sep 17 00:00:00 2001 From: k2kite-megankim Date: Sun, 3 May 2026 23:35:05 +0900 Subject: [PATCH 2/4] chore(actionlint): pin installer to v1.7.12 + explicit permissions + comment fix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-review LOW items applied: 1. Installer script ref `main` → `v1.7.12` — pinning the binary version arg alone left upstream free to rewrite the installer script itself. Both refs now pinned to the same release. 2. Explicit `permissions: { contents: read }` block. Inheriting from org default works today but the explicit block makes the read-only intent self-documenting in the workflow file. 3. Header comment "three reusable workflows" → "three + this actionlint.yml itself" since actionlint auto-discovers all .github/workflows/*.yml from the repo root and lints itself too. No behavior change beyond the supply-chain tightening. --- .github/workflows/actionlint.yml | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml index 8bc198b..341a55f 100644 --- a/.github/workflows/actionlint.yml +++ b/.github/workflows/actionlint.yml @@ -10,9 +10,10 @@ name: actionlint # expression parser catches this class of bug at PR review time # instead of post-merge tag-push. # -# Coverage: `marketplace-publish.yml`, `plugin-ci.yml`, and -# `validate-plugin-manifest.yml` — the three reusable workflows -# this repo owns. +# Coverage: `marketplace-publish.yml`, `plugin-ci.yml`, +# `validate-plugin-manifest.yml` (the three reusable workflows this +# repo owns) plus this `actionlint.yml` itself — actionlint +# auto-discovers all `.github/workflows/*.yml` from the repo root. # # Self-hosted runner labels (`oracle`, ...) are whitelisted # in `.github/actionlint.yaml` so the linter doesn't flag them as @@ -23,6 +24,9 @@ on: push: branches: [main] +permissions: + contents: read + jobs: lint: runs-on: ubuntu-latest @@ -33,9 +37,11 @@ jobs: # Use the upstream installer script (rhysd/actionlint owner- # maintained) rather than a third-party wrapper action — keeps # the supply chain to one source of truth + a known-version - # binary. Pinned via the script's first arg. + # binary. Both the script ref AND the binary version are + # pinned to v1.7.12 — `main` would let upstream silently + # rewrite the installer between runs. run: | - curl -sL https://raw.githubusercontent.com/rhysd/actionlint/main/scripts/download-actionlint.bash \ + curl -sL https://raw.githubusercontent.com/rhysd/actionlint/v1.7.12/scripts/download-actionlint.bash \ | bash -s -- 1.7.12 ./actionlint -version From 7743fec2f538f87f5126140a2b0c23dd3624559a Mon Sep 17 00:00:00 2001 From: k2kite-megankim Date: Sun, 3 May 2026 23:37:41 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix(actionlint):=20-color=3Dnever=20?= =?UTF-8?q?=E2=86=92=20-no-color=20(correct=20flag=20spelling)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI ran the workflow and actionlint exited 2 with a help dump because `-color=never` is not a recognized flag. The actionlint CLI uses boolean toggles: `-color` (force on) and `-no-color` (force off) — no `=value` form. Caught by the workflow itself on first PR run, exactly the kind of issue this workflow exists to surface (though it caught its own typo rather than other workflows', which is also fine). --- .github/workflows/actionlint.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/actionlint.yml b/.github/workflows/actionlint.yml index 341a55f..03e4572 100644 --- a/.github/workflows/actionlint.yml +++ b/.github/workflows/actionlint.yml @@ -46,7 +46,8 @@ jobs: ./actionlint -version - name: Run actionlint - # `-color=never` for cleaner CI logs. + # `-no-color` for cleaner CI logs (actionlint's flag is + # boolean-style, not `-color=never`). # actionlint auto-discovers `.github/actionlint.yaml` for # self-hosted runner label whitelisting. - run: ./actionlint -color=never + run: ./actionlint -no-color From d80eb915ea732b5d3f6069bc21758565f8eb7a48 Mon Sep 17 00:00:00 2001 From: k2kite-megankim Date: Sun, 3 May 2026 23:39:34 +0900 Subject: [PATCH 4/4] fix(marketplace-publish): silence SC2016 in node -e resolver block MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit actionlint's bundled shellcheck flags SC2016 ("Expressions don't expand in single quotes") on the resolver step's `node -e '...'` block. False positive — the `$` chars inside the single-quoted JS body are regex anchors (`/(^|\/)\.env($|\.|\/)/` etc.) and `process.env.X` accesses, not shell variable interpolation. Adds inline `# shellcheck disable=SC2016` directive scoped to that single run block. Comment explains the false-positive context so future readers know why the rule is suppressed. --- .github/workflows/marketplace-publish.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/marketplace-publish.yml b/.github/workflows/marketplace-publish.yml index babfece..7b9efdf 100644 --- a/.github/workflows/marketplace-publish.yml +++ b/.github/workflows/marketplace-publish.yml @@ -191,6 +191,10 @@ jobs: # protection — see "tag commit reachable from origin/main" # step above. run: | + # shellcheck disable=SC2016 + # SC2016 false positive — `$` inside the single-quoted + # `node -e '...'` body is JS regex anchor (`/.../`) and + # `process.env.X` access, not shell variable interpolation. node -e ' const fs = require("node:fs"); const path = require("node:path");