ci workflow (fastify/fastify)
The ci workflow from fastify/fastify, explained and optimized by Latchkey.
CI health: B - good
Point runs-on at Latchkey and get job timeouts, SHA-pinned actions, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the ci workflow from the fastify/fastify repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: ci
on:
push:
branches:
- main
- next
- '*.x'
paths-ignore:
- 'docs/**'
- '*.md'
pull_request:
paths-ignore:
- 'docs/**'
- '*.md'
workflow_dispatch:
inputs:
nodejs-version:
description: 'Node.js version to use (e.g., 24.0.0-rc.1)'
required: true
type: string
# This allows a subsequently queued workflow run to interrupt previous runs
concurrency:
group: "${{ github.workflow }}-${{ github.event.pull_request.head.label || github.head_ref || github.ref }}"
cancel-in-progress: true
permissions:
contents: read
jobs:
quality-check:
uses: fastify/workflows/.github/workflows/nested-quality.yml@v6
permissions:
contents: read
with:
license-check: true
lint: true
coverage-nix:
needs:
- quality-check
permissions:
contents: read
uses: ./.github/workflows/coverage-nix.yml
coverage-win:
needs:
- quality-check
permissions:
contents: read
uses: ./.github/workflows/coverage-win.yml
test-unit:
needs:
- quality-check
- coverage-nix
- coverage-win
runs-on: ${{ matrix.os }}
permissions:
contents: read
strategy:
matrix:
node-version: [20, 22, 24, 26]
os: [macos-latest, ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
cache-dependency-path: package.json
check-latest: true
- name: Install
run: |
npm install --ignore-scripts
- name: Run tests
run: |
npm run unit
# Useful for testing Release Candidates of Node.js
test-unit-custom:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Use Custom Node.js Version
uses: actions/setup-node@v6
with:
node-version: ${{ github.event.inputs.nodejs-version }}
cache: 'npm'
cache-dependency-path: package.json
check-latest: true
- name: Install
run: |
npm install --ignore-scripts
- name: Run tests
run: |
npm run unit
test-types:
needs:
- quality-check
- coverage-nix
- coverage-win
runs-on: 'ubuntu-latest'
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: package.json
check-latest: true
- name: Install
run: |
npm install --ignore-scripts
- name: Run type tests
run: |
npm run test:types
env:
NODE_OPTIONS: no-network-family-autoselection
test-pino-compat:
name: Test pino compatibility
needs:
- quality-check
- coverage-nix
- coverage-win
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
pino-version: ['^9', '^10']
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v6
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: package.json
check-latest: true
- name: Install dependencies
run: |
npm install --ignore-scripts
- name: Install pino ${{ matrix.pino-version }}
run: |
npm install --ignore-scripts --no-save pino@${{ matrix.pino-version }}
- name: Run unit tests
run: |
npm run unit
- name: Run type tests
run: |
npm run test:types
env:
NODE_OPTIONS: no-network-family-autoselection
package:
needs:
- test-types
- test-unit
- test-pino-compat
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- name: Use Node.js
uses: actions/setup-node@v6
with:
node-version: 'lts/*'
cache: 'npm'
cache-dependency-path: package.json
check-latest: true
- name: install fastify
run: |
npm install --ignore-scripts
- name: install webpack stack
run: |
cd test/bundler/webpack && npm install --ignore-scripts
- name: Test webpack bundle
run: |
cd test/bundler/webpack && npm run test
- name: install esbuild stack
run: |
cd test/bundler/esbuild && npm install --ignore-scripts
- name: Test esbuild bundle
run: |
cd test/bundler/esbuild && npm run test
automerge:
if: >
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.event.pull_request.user.login == 'dependabot[bot]'
needs:
- test-types
- test-unit
- package
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: write
steps:
- uses: fastify/github-action-merge-dependabot@v3
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
name: ci on: push: branches: - main - next - '*.x' paths-ignore: - 'docs/**' - '*.md' pull_request: paths-ignore: - 'docs/**' - '*.md' workflow_dispatch: inputs: nodejs-version: description: 'Node.js version to use (e.g., 24.0.0-rc.1)' required: true type: string # This allows a subsequently queued workflow run to interrupt previous runs concurrency: group: "${{ github.workflow }}-${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" cancel-in-progress: true permissions: contents: read jobs: quality-check: timeout-minutes: 30 uses: fastify/workflows/.github/workflows/nested-quality.yml@v6 permissions: contents: read with: license-check: true lint: true coverage-nix: timeout-minutes: 30 needs: - quality-check permissions: contents: read uses: ./.github/workflows/coverage-nix.yml coverage-win: timeout-minutes: 30 needs: - quality-check permissions: contents: read uses: ./.github/workflows/coverage-win.yml test-unit: timeout-minutes: 30 needs: - quality-check - coverage-nix - coverage-win runs-on: ${{ matrix.os }} permissions: contents: read strategy: matrix: node-version: [20, 22, 24, 26] os: [macos-latest, ubuntu-latest, windows-latest] steps: - uses: actions/checkout@v7 with: persist-credentials: false - name: Use Node.js uses: actions/setup-node@v6 with: node-version: ${{ matrix.node-version }} cache: 'npm' cache-dependency-path: package.json check-latest: true - name: Install run: | npm install --ignore-scripts - name: Run tests run: | npm run unit # Useful for testing Release Candidates of Node.js test-unit-custom: timeout-minutes: 30 if: github.event_name == 'workflow_dispatch' runs-on: latchkey-small permissions: contents: read steps: - uses: actions/checkout@v7 with: persist-credentials: false - name: Use Custom Node.js Version uses: actions/setup-node@v6 with: node-version: ${{ github.event.inputs.nodejs-version }} cache: 'npm' cache-dependency-path: package.json check-latest: true - name: Install run: | npm install --ignore-scripts - name: Run tests run: | npm run unit test-types: timeout-minutes: 30 needs: - quality-check - coverage-nix - coverage-win runs-on: 'ubuntu-latest' permissions: contents: read steps: - uses: actions/checkout@v7 with: persist-credentials: false - uses: actions/setup-node@v6 with: node-version: '20' cache: 'npm' cache-dependency-path: package.json check-latest: true - name: Install run: | npm install --ignore-scripts - name: Run type tests run: | npm run test:types env: NODE_OPTIONS: no-network-family-autoselection test-pino-compat: timeout-minutes: 30 name: Test pino compatibility needs: - quality-check - coverage-nix - coverage-win runs-on: latchkey-small permissions: contents: read strategy: matrix: pino-version: ['^9', '^10'] steps: - uses: actions/checkout@v7 with: persist-credentials: false - uses: actions/setup-node@v6 with: node-version: '20' cache: 'npm' cache-dependency-path: package.json check-latest: true - name: Install dependencies run: | npm install --ignore-scripts - name: Install pino ${{ matrix.pino-version }} run: | npm install --ignore-scripts --no-save pino@${{ matrix.pino-version }} - name: Run unit tests run: | npm run unit - name: Run type tests run: | npm run test:types env: NODE_OPTIONS: no-network-family-autoselection package: timeout-minutes: 30 needs: - test-types - test-unit - test-pino-compat runs-on: latchkey-small permissions: contents: read steps: - uses: actions/checkout@v7 with: persist-credentials: false - name: Use Node.js uses: actions/setup-node@v6 with: node-version: 'lts/*' cache: 'npm' cache-dependency-path: package.json check-latest: true - name: install fastify run: | npm install --ignore-scripts - name: install webpack stack run: | cd test/bundler/webpack && npm install --ignore-scripts - name: Test webpack bundle run: | cd test/bundler/webpack && npm run test - name: install esbuild stack run: | cd test/bundler/esbuild && npm install --ignore-scripts - name: Test esbuild bundle run: | cd test/bundler/esbuild && npm run test automerge: timeout-minutes: 30 if: > github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository && github.event.pull_request.user.login == 'dependabot[bot]' needs: - test-types - test-unit - package runs-on: latchkey-small permissions: pull-requests: write contents: write steps: - uses: fastify/github-action-merge-dependabot@v3 with: github-token: ${{ secrets.GITHUB_TOKEN }}
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Add a job timeout so a hung step cannot burn hours of runner time.
2 third-party actions are referenced by a movable tag. Pin them to the commit SHA (Latchkey resolves and applies this automatically) so a repointed tag cannot change what runs.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Dependency installs
This workflow runs 9 jobs (21 with the matrix expanded) per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.