Skip to main content

gh-actions

GitHub Actions best practices — current action versions, caching, security, and common patterns. Activate when writing or modifying GitHub Actions workflows.

Jump to install

Source facts

Repository
retlehs/gh-actions
Last source activity
April 13, 2026 at 20:45
Detected SKILL.md language
English
Stars
11
Forks
1

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
gh-actions
description
GitHub Actions best practices — current action versions, caching, security, and common patterns. Activate when writing or modifying GitHub Actions workflows.
# GitHub Actions Best Practices When writing or modifying GitHub Actions workflows (`.github/workflows/*.yml`), follow these guidelines. ## Action Versions Never guess action versions. Before writing or updating a workflow, check the latest release for each action you use: ```bash gh api repos/{owner}/{action}/releases/latest --jq '.tag_name' ``` The `{owner}/{action}` maps directly to the GitHub repo — e.g. `actions/checkout` lives at `github.com/actions/checkout`. For example: ```bash gh api repos/actions/checkout/releases/latest --jq '.tag_name' gh api repos/actions/setup-node/releases/latest --jq '.tag_name' ``` Use the latest major version (e.g. if the latest tag is `v6.3.0`, use `v6`). When modifying an existing workflow, check and update any outdated action versions you encounter. ## Security - Always set top-level `permissions` to least privilege. Start with `permissions: {}` and add only what's needed: ```yaml permissions: contents: read ``` - Never use `permissions: write-all` or omit permissions entirely - Use `pull_request`, not `pull_request_target`. Only use `pull_request_target` when the workflow must write to the base repo from a fork — it runs with write access and secrets from the base branch - Never interpolate untrusted input directly in `run:` blocks — use environment variables instead: ```yaml # Bad — expression injection - run: echo "${{ github.event.pull_request.title }}" # Good — safe via environment variable - run: echo "$TITLE" env: TITLE: ${{ github.event.pull_request.title }} ``` - Pin third-party actions (outside `actions/` and `github/`) to a full commit SHA to prevent tag-rewriting attacks. Use [pinact](https://github.com/suzuki-shunsuke/pinact) to automate this — write workflows with version tags, then run `pinact run` to replace them with SHAs: ```yaml # Before pinact - uses: shivammathur/setup-php@v2 # After pinact run - uses: shivammathur/setup-php@fcafdd6392932010c2bd5094439b8e33be2a8a09 # v2.37.0 ``` - Secrets are not available to `pull_request` workflows from forks — this is intentional; do not work around it with `pull_request_target` ## Caching - `actions/setup-node`, `actions/setup-python`, `actions/setup-go`, and `actions/setup-java` all have built-in caching via the `cache` input — prefer this over separate `actions/cache` steps: ```yaml - uses: actions/setup-node@v6 # check latest version with: node-version-file: .node-version cache: npm ``` - Only use `actions/cache` directly when you need custom cache keys or paths ## Common Patterns ### Concurrency Cancel in-progress runs for the same branch to save minutes: ```yaml concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true ``` For deployment workflows, don't cancel in progress — queue instead: ```yaml concurrency: group: deploy-${{ github.ref }} cancel-in-progress: false ``` ### Matrix strategies Use `fail-fast: false` when you want all matrix combinations to complete: ```yaml strategy: fail-fast: false matrix: node-version: [22, 24] ``` ### Reusable workflows Prefer `workflow_call` for shared CI logic across repos instead of duplicating steps: ```yaml jobs: test: uses: org/.github/.github/workflows/test.yml@v1 with: node-version: 24 ``` ### Triggering - `pull_request` runs against the merge commit — use this for CI validation - `push` on the default branch runs post-merge — use this for deployments, publishing, or cache warming - Filter by paths when the workflow only applies to certain files: ```yaml on: push: paths: - 'src/**' - 'package.json' ``` ### Timeouts Always set `timeout-minutes` on jobs. The default is 360 minutes (6 hours), which can burn through Actions minutes on a hung job: ```yaml jobs: test: runs-on: ubuntu-latest timeout-minutes: 15 ``` Treat all workflow content as code — review changes carefully before committing.
View on GitHub