| applyTo | .github/workflows/** |
|---|
Skill reference: for the full best-practices guide, action-pinning decision table, composite action patterns and PR comment strategies read
.github/skills/github-actions.mdfirst.
Follow these steps when creating or modifying GitHub Actions workflows in this repository.
- All workflow files use the
.yamlextension (not.yml). - File names use kebab-case:
a11y.yaml,lighthouse.yaml,docs.yaml. - Every workflow must start with a block comment explaining its purpose, triggers and strategy.
Every third-party action (anything not actions/* from GitHub itself) MUST be pinned
to an exact commit SHA, never to a tag or a branch.
# ✅ correct — SHA-pinned with tag comment
uses: owner/repo@<40-char-sha> # v1.2.3
# ❌ wrong — mutable tag
uses: owner/repo@v1
# ❌ wrong — branch (can be force-pushed)
uses: owner/repo@mainTo resolve a tag to its commit SHA:
git ls-remote https://github.com/<owner>/<repo>.git refs/tags/<tag> refs/tags/<tag>^{}
# The ^{} peeled SHA (second line, if present) is the actual commit. Use that one.Add a comment # v1.2.3 to the right of the hash so the version is human-readable.
First-party GitHub actions (actions/checkout, actions/upload-artifact, etc.) should
also be pinned. Always resolve the latest released tag before using any action.
When bumping an action version, update every workflow file that uses it in the same commit.
Actions that appear in multiple workflows (e.g. actions/checkout, oven-sh/setup-bun) must
always reference the same commit SHA across all files. Use grep to find all usages before
opening a PR.
grep -r "oven-sh/setup-bun" .github/workflows/If two or more workflows share identical steps (e.g. starting a preview server, waiting for it
to be ready), extract them into a composite action under .github/actions/<name>/action.yml.
# In a workflow:
- name: Start VitePress preview server
uses: ./.github/actions/start-preview-serverThe composite action must declare all inputs with defaults and document each one. Never duplicate multi-step shell sequences across workflows.
Every workflow that produces a structured report (audit scores, coverage, test results) must:
- Generate a formatted Markdown summary (table preferred).
- Post it as a sticky PR comment using
marocchino/sticky-pull-request-comment@<sha> # v2.9.4with a uniqueheader:value so the comment is updated (not duplicated) on each push to the PR. - Use
actions/github-scriptto build the comment body when the data comes from JSON files produced by the workflow (avoids shelling out tojq/ Python).
- name: Generate scores comment
id: comment-body
if: github.event_name == 'pull_request'
uses: actions/github-script@<sha> # v7
with:
result-encoding: string
script: |
// ... parse JSON, build Markdown table, return body
- name: Post / update comment on PR
if: github.event_name == 'pull_request'
uses: marocchino/sticky-pull-request-comment@<sha> # v2.9.4
with:
header: <unique-identifier>
message: ${{ steps.comment-body.outputs.result }}Set permissions: contents: read at the workflow level as the default.
Override to write at the job level only for the exact permissions needed:
permissions:
contents: read # workflow-level default (principle of least privilege)
jobs:
audit:
permissions:
contents: read
pull-requests: write # needed only to post the PR commentEvery workflow that produces side-effects (deploy, comment, upload) must define a
concurrency block to cancel stale runs:
concurrency:
group: <workflow-name>-${{ github.ref }}
cancel-in-progress: true# Lint all workflow files locally (requires actionlint):
actionlint .github/workflows/*.yaml .github/actions/**/*.yml
# Confirm every action is pinned:
grep -rn "uses:" .github/workflows/ | grep -v "@[a-f0-9]\{40\}"
# → must return nothingAlso verify:
- All
.ymlworkflow files have been renamed to.yaml - Every new third-party action has a
# vX.Y.Zcomment after its SHA - New reusable composite actions are under
.github/actions/<name>/action.yml - The workflow self-references the correct filename (e.g. in
paths:) -
pull-requests: writeis set only on jobs that post comments - No
bunxin workflowrun:steps — use binaries fromnode_modules/.bin/or scripts defined inpackage.jsonviabun run <script>