mcpField
A
actions-ci-tuning

actions-ci-tuning

github.com/mizchiCloud & DevOps41645 installsUpdated 1w ago
Repository

About actions-ci-tuning

A skill for auditing and improving GitHub Actions workflows, aimed at teams maintaining CI. It prioritizes correctness before checking caching, parallel tests, artifact guards, reliability, and security configuration, then proposes minimal changes.

Key features

  1. 01Inventory workflows and run durations
  2. 02Check dependency caching and installation
  3. 03Review parallel jobs and test sharding
  4. 04Guard artifact report merging
  5. 05Check concurrency, triggers, and version pins

Use cases

  1. 01Diagnose failing or slow CI runs
  2. 02Tune Playwright test sharding and reports
  3. 03Reduce cache misses and lockfile drift
  4. 04Review GitHub Actions security settings

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/mizchi/skills --skill actions-ci-tuning

After installing, type/actions-ci-tuningin Claude Code to confirm it loaded.

Bundled filesREADME.md

Details

From the project README

GitHub Actions CI Tuning

Audit and improve GitHub Actions workflows. Focus on correctness first (jobs that error rather than fail), then speed (cache, parallelism), then reliability (flakiness, artifact guards).


Workflow

  1. Inventory — list all .github/workflows/*.yml files and their trigger events, jobs, and rough durations (gh run list --workflow <file> --limit 5).
  2. Audit against checklist — run through each section below and flag every gap.
  3. Prioritise — group findings by impact: correctness bugs > cache misses > parallelism gains > cosmetic.
  4. Propose changes — draft minimal diffs; do not refactor unrelated parts.
  5. Verify — after applying, confirm via gh run list that the next run is green and faster.

Checklist

Package Manager Cache

A cache miss means re-downloading hundreds of packages on every run. This is the single highest-ROI fix in most repos.

pnpm

- uses: pnpm/action-setup@v6
  with:
    version: latest           # or pin to a specific version

- uses: actions/setup-node@v7
  with:
    node-version-file: .nvmrc  # or node-version: '24' (current LTS)
    cache: 'pnpm'              # ← must be present; omitting it silently skips caching

Common mistake: calling corepack enable instead of pnpm/action-setup. corepack enable does NOT set up the pnpm store cache — actions/setup-node cache: 'pnpm' requires pnpm/action-setup to have run first.

npm

- uses: actions/setup-node@v7
  with:
    node-version-file: .nvmrc
    cache: 'npm'

yarn (classic / berry)

- uses: actions/setup-node@v7
  with:
    node-version-file: .nvmrc
    cache: 'yarn'

Verify cache is working: look for Cache restored successfully in setup-node logs. If absent, cache: key is likely missing or the lockfile path is wrong.


Dependency Install
  • Always use --frozen-lockfile (pnpm) / --ci (npm) / --immutable (yarn berry) in CI. Prevents lockfile drift from silently changing the installed tree.
  • Never use npm install or pnpm install without the frozen flag in CI.

Parallel Jobs vs. Steps
  • Independent jobs (lint, typecheck, test) should be separate jobs so they run in parallel, not sequential steps.
  • Sequential steps are fine for setup within a single job (install → build → test).
  • If a repo has lint + typecheck + unit test all in one job, split them.
jobs:
  lint:
    runs-on: ubuntu-latest
    steps: [checkout, setup, install, run lint]
  typecheck:
    runs-on: ubuntu-latest
    steps: [checkout, setup, install, run typecheck]
  test:
    runs-on: ubuntu-latest
    steps: [checkout, setup, install, run test]

Test Sharding (Playwright / Vitest)

Use sharding to cut long test suites. Each shard runs a subset of tests in parallel.

Playwright example — 4 shards:

jobs:
  e2e:
    strategy:
      fail-fast: false
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - run: pnpm exec playwright test --shard=${{ matrix.shard }}/4
        env:
          CI: true
      - uses: actions/upload-artifact@v7
        if: ${{ !cancelled() }}       # ← upload even on test failure
        with:
          name: blob-report-${{ matrix.shard }}
          path: blob-report/
          retention-days: 1

Pitfalls:

  • fail-fast: false is required — otherwise one failing shard cancels the rest before they upload blob reports.
  • if: ${{ !cancelled() }} on the upload step ensures blob reports are uploaded even when tests fail. Without this, the merge-reports job gets no artifacts.

Artifact Guard in merge-reports

When shards can fail before uploading blob reports, the all-blob-reports directory may not exist. Guard the merge command:

- name: Merge reports
  run: |
    if [ -d all-blob-reports ] && [ "$(ls -A all-blob-reports 2>/dev/null)" ]; then
      pnpm exec playwright merge-reports --reporter=html ./all-blob-reports
    else
      echo "No blob reports found, skipping merge"
      mkdir -p playwright-report
      echo "<html><body><p>No test results available</p></body></html>" > playwright-report/index.html
    fi

Without this guard, the merge-reports job errors with Error: Directory does not exist: ./all-blob-reports even when the overall workflow should gracefully report "no results."


Playwright Docker Image

For VRT (Visual Regression Testing), snapshots must be generated in the same environment as CI (Linux + specific fonts). Use the official Playwright Docker image instead of a plain node:* image:

mcr.microsoft.com/playwright:v1.59.1-noble   # pin to the same version as @playwright/test

Why not node:24?

  • node:24 requires playwright install --with-deps chromium which triggers apt-get and can hang for hours in Docker Desktop on macOS (known issue with apt in QEMU-emulated environments).
  • The mcr.microsoft.com/playwright image ships Chromium and all system dependencies pre-installed — no apt-get needed, starts in seconds.

Version pinning: keep the image version in sync with @playwright/test in package.json. Mismatch causes browser-not-found errors.


Concurrency Control

Cancel in-progress runs for the same branch/PR to avoid queue buildup:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

Exception: do NOT cancel release/deploy workflows on main. Scope it to PR branches only:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}

Scheduled Workflow Reliability
  • Scheduled workflows on GitHub Actions can silently stop firing if the repo has no activity for 60 days.
  • Add a workflow_dispatch: trigger alongside schedule: so it can be manually re-triggered without a code change.
  • For critical scheduled jobs (e.g., nightly E2E), add a failure notification step (GitHub issue creation, Slack, etc.) so silent failures are visible.
on:
  schedule:
    - cron: '0 0 * * *'
  workflow_dispatch:      # ← always add this

Action Version Pinning
  • Pin third-party actions to a full commit SHA, not a tag. Tags can be moved.
  • Use a comment with the human-readable version for readability:
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
  • Dependabot or Renovate can keep SHA pins up to date automatically. Check that .github/dependabot.yml includes the github-actions ecosystem.

Node.js 20 deprecation warning: GitHub started issuing "Node.js 20 actions are deprecated" warnings in 2025/2026. This refers to the action's own runtime (runs.using: node20), not the project's Node version. Fix by upgrading to the first major version that uses node24:

actionfirst node24 majorcurrent major (2026-09)
actions/checkoutv6.0.0v7
actions/setup-nodev6.0.0v7
actions/cachev5.0.0v6
actions/upload-artifactv7.0.0v7
actions/download-artifactv8.0.0v8
aws-actions/configure-aws-credentialsv6.0.0v6

Latest pinned SHAs (verified 2026-09):

uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
uses: aws-actions/configure-aws-credentials@cbe3b392738ccf3f987d68400dafcf4b0624a56c # v6.2.4

Note: SHAs drift — always verify with gh release view --repo <owner>/<action> before pinning.


Environment Variables
  • CI: true should be set at the job or workflow level, not only in individual run steps. Many tools (Playwright, Vite, etc.) change behavior based on this flag.
  • Secrets should use ${{ secrets.NAME }} — never hardcode tokens.
  • NODE_OPTIONS: --max-old-space-size=4096 is sometimes needed for large builds in constrained runners (default heap is ~1.5 GB for a 7 GB runner).

Quick Audit Commands

# List recent run durations for a workflow
gh run list --workflow e2e.yml --limit 10 --json databaseId,displayTitle,createdAt,updatedAt,conclusion \
  | jq '.[] | {title: .displayTitle, duration: (.updatedAt | fromdate) - (.createdAt | fromdate), conclusion: .conclusion}'

# Find jobs that always time out
gh run list --workflow e2e.yml --status failure --limit 20 --json databaseId \
  | jq -r '.[].databaseId' \
  | xargs -I{} gh run view {} --json jobs \
  | jq '.jobs[] | select(.conclusion == "timed_out") | .name'

# Check cache usage in a run
gh run view <run_id> --log | grep -i "cache"

Common Anti-Patterns

Anti-patternProblemFix
corepack enable only (no pnpm/action-setup)pnpm store is not cachedAdd pnpm/action-setup@v6 before setup-node
pnpm install without --frozen-lockfileLockfile can silently driftUse --frozen-lockfile always in CI
fail-fast: true on test matrixShards cancel before uploading artifactsSet fail-fast: false
Upload artifact without if: ${{ !cancelled() }}Blob reports lost on test failureAdd the if condition
playwright merge-reports without directory guardJob errors when no shards uploadedGuard with [ -d all-blob-reports ] check
node:* image for Playwright VRTapt-get hangs in Docker Desktop on macOSUse mcr.microsoft.com/playwright image
Scheduled workflow without workflow_dispatchCan't manually re-triggerAlways add workflow_dispatch:
Action pinned to tag not SHATag can be moved (supply-chain risk)Pin to full commit SHA

Comments

Markdown supported