---
description: GitHub Actions workflow conventions
globs: [".github/workflows/*.{yml,yaml}",".github/actions/**"]
alwaysApply: false
---

# GitHub Actions

- Set least-privilege `permissions:` at the workflow top (`contents: read`) and widen per job only where needed.
- Pin third-party actions to a full commit SHA with the version in a comment (`uses: actions/checkout@<sha> # v4.2.2`). First-party `actions/*` may use major tags only if the repo already does.
- Never interpolate untrusted input (`github.event.issue.title`, PR branch names, comment bodies) directly into `run:`. Pass it through `env:` and quote `"$VAR"`.
- Avoid `pull_request_target` with a checkout of the PR head. If it's unavoidable, don't run the PR's code with secrets.
- Cloud auth via OIDC (`id-token: write` + the provider's login action), not long-lived keys in secrets.
- Use built-in caching (`actions/setup-node` `cache: pnpm`, `setup-python` `cache: pip`) before writing custom `actions/cache` steps.
- Add `concurrency: { group: ${{ github.workflow }}-${{ github.ref }}, cancel-in-progress: true }` to PR workflows.
- Set `timeout-minutes` on every job. Use `matrix` for versions; `fail-fast: false` when results per entry matter.
- Reuse logic with reusable workflows (`workflow_call`) or composite actions instead of copy-paste.
- Path filters (`paths:`) to skip unrelated jobs in monorepos.

## Don't
- Don't echo secrets, or use `set -x` in steps that touch them.

## Done when
`actionlint` passes (and `zizmor .github/workflows` if installed).
