skills/ci-cd-ops/SKILL.md
CI/CD pipeline patterns with GitHub Actions, release automation, and testing strategies. Use for: github actions, workflow, CI, CD, pipeline, deploy, release, semantic release, changesets, goreleaser, matrix, cache, secrets, environment, artifact, reusable workflow, composite action.
npx skillsauth add 0xDarkMatter/claude-mods ci-cd-opsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Comprehensive patterns for continuous integration, delivery, and deployment using GitHub Actions, release automation tools, and testing pipelines.
name: CI # Display name in Actions tab
on: # Trigger events
push:
branches: [main]
pull_request:
branches: [main]
permissions: # GITHUB_TOKEN scope (least privilege)
contents: read
pull-requests: write
concurrency: # Prevent duplicate runs
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env: # Workflow-level environment variables
NODE_VERSION: "20"
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: npm
- run: npm ci
- run: npm test
| Element | Purpose | Example |
|---------|---------|---------|
| on | Event triggers | push, pull_request, schedule |
| jobs.<id>.runs-on | Runner selection | ubuntu-latest, self-hosted |
| jobs.<id>.needs | Job dependencies | needs: [build, lint] |
| jobs.<id>.if | Conditional execution | if: github.event_name == 'push' |
| jobs.<id>.strategy.matrix | Parallel variants | node-version: [18, 20, 22] |
| jobs.<id>.environment | Deployment target | environment: production |
| jobs.<id>.permissions | Token scope | contents: write |
| steps[*].uses | Use an action | uses: actions/checkout@v4 |
| steps[*].run | Run a command | run: npm test |
| steps[*].env | Step environment | env: { CI: true } |
| Scenario | Trigger | Config |
|----------|---------|--------|
| Run tests on every PR | pull_request | branches: [main] |
| Deploy on merge to main | push | branches: [main] |
| Release on version tag | push | tags: ['v*'] |
| Nightly builds | schedule | cron: '0 2 * * *' |
| Manual deployment | workflow_dispatch | inputs: { environment: ... } |
| Called by another workflow | workflow_call | inputs:, secrets: |
| On PR label change | pull_request | types: [labeled] |
| On issue comment | issue_comment | types: [created] |
| On release published | release | types: [published] |
| On package push | registry_package | types: [published] |
on:
push:
branches: [main, 'release/**'] # Branch patterns
paths: ['src/**', '!src/**/*.test.*'] # Path filters (ignore tests)
tags: ['v*'] # Tag patterns
pull_request:
types: [opened, synchronize, reopened] # Default types
paths-ignore: ['docs/**', '*.md'] # Ignore docs-only changes
| Ecosystem | Action / Key | Path | Restore Key |
|-----------|-------------|------|-------------|
| Node (npm) | actions/setup-node with cache: npm | Auto | Auto |
| Node (pnpm) | actions/setup-node with cache: pnpm | Auto | Auto |
| Go modules | actions/setup-go with cache: true | Auto | Auto |
| Cargo | actions/cache@v4 | ~/.cargo/registry, target | cargo-${{ runner.os }}-${{ hashFiles('Cargo.lock') }} |
| pip / uv | actions/setup-python with cache: pip | Auto | Auto |
| Docker layers | docker/build-push-action | Uses buildx cache | type=gha or type=registry |
| Gradle | actions/setup-java with cache: gradle | Auto | Auto |
| Composer | actions/cache@v4 | vendor | composer-${{ hashFiles('composer.lock') }} |
- uses: actions/cache@v4
with:
path: |
~/.cargo/bin
~/.cargo/registry
~/.cargo/git
target
key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
cargo-${{ runner.os }}-
strategy:
fail-fast: false # Don't cancel siblings on failure
max-parallel: 4 # Limit concurrent jobs
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20, 22]
include: # Add specific combos
- os: ubuntu-latest
node-version: 22
coverage: true
exclude: # Remove specific combos
- os: windows-latest
node-version: 18
prepare:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set.outputs.matrix }}
steps:
- id: set
run: echo "matrix=$(jq -c . matrix.json)" >> "$GITHUB_OUTPUT"
test:
needs: prepare
strategy:
matrix: ${{ fromJson(needs.prepare.outputs.matrix) }}
| Scope | Access | Use Case | |-------|--------|----------| | Repository secrets | All workflows in repo | API keys, tokens | | Environment secrets | Jobs targeting that environment | Production credentials | | Organization secrets | Selected repos in org | Shared service accounts | | OIDC tokens | Federated identity | Cloud deployment (no stored secrets) |
# Reference secrets - NEVER echo or log them
- run: deploy --token ${{ secrets.DEPLOY_TOKEN }}
# Mask custom values
- run: echo "::add-mask::$CUSTOM_SECRET"
# Use environments for deployment secrets
jobs:
deploy:
environment: production # Requires approval + has secrets
steps:
- run: deploy --key ${{ secrets.PROD_API_KEY }}
permissions:
id-token: write
contents: read
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/github-actions
aws-region: us-east-1
name: Test
on:
pull_request:
branches: [main]
concurrency:
group: test-${{ github.head_ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npm run lint
- run: npm test -- --coverage
name: Deploy
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- run: npx wrangler deploy
env:
CLOUDFLARE_API_TOKEN: ${{ secrets.CF_API_TOKEN }}
name: Release
on:
push:
tags: ['v*']
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- run: |
gh release create ${{ github.ref_name }} \
--generate-notes \
--title "${{ github.ref_name }}"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
| Gotcha | Problem | Fix |
|--------|---------|-----|
| Shallow clone | git describe fails, history missing | actions/checkout@v4 with fetch-depth: 0 |
| Default permissions | GITHUB_TOKEN is read-only by default | Set permissions: explicitly |
| Action pinning | @main can break without warning | Pin to SHA: @abc123 or @v4 |
| Fork PR secrets | Secrets unavailable on fork PRs | Use pull_request_target carefully |
| Concurrent deploys | Race condition on production | Use concurrency: groups |
| Stale caches | Cache grows unbounded | Include lockfile hash in key |
| Node.js version | setup-node defaults vary | Always specify node-version |
| Docker layer cache | Rebuilds everything without cache | Use cache-from: type=gha |
| Matrix + environment | Each matrix job needs approval | Use a single deploy job after matrix |
| Path filters + required checks | Skipped jobs block merge | Use paths-filter action or make checks non-required |
| GITHUB_TOKEN in PRs | Cannot trigger other workflows | Use a PAT or GitHub App token |
| Windows line endings | Scripts fail with \r\n | Use .gitattributes or core.autocrlf |
| Expression | Result |
|------------|--------|
| ${{ github.event_name }} | push, pull_request, etc. |
| ${{ github.ref_name }} | Branch or tag name |
| ${{ github.sha }} | Full commit SHA |
| ${{ github.actor }} | User who triggered |
| ${{ runner.os }} | Linux, Windows, macOS |
| ${{ contains(github.event.head_commit.message, '[skip ci]') }} | Check commit message |
| ${{ needs.build.outputs.version }} | Output from prior job |
| ${{ fromJson(steps.meta.outputs.json) }} | Parse JSON output |
| ${{ hashFiles('**/package-lock.json') }} | Hash for cache keys |
| ${{ format('refs/heads/{0}', matrix.branch) }} | String formatting |
| ${{ toJson(matrix) }} | Debug: print matrix config |
steps:
- id: version
run: echo "value=$(cat VERSION)" >> "$GITHUB_OUTPUT"
- run: echo "Version is ${{ steps.version.outputs.value }}"
jobs:
build:
runs-on: ubuntu-latest
outputs:
artifact-id: ${{ steps.upload.outputs.artifact-id }}
steps:
- id: upload
run: echo "artifact-id=abc123" >> "$GITHUB_OUTPUT"
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Deploying ${{ needs.build.outputs.artifact-id }}"
| File | Contents |
|------|----------|
| references/github-actions.md | Complete workflow syntax, reusable workflows, composite actions, OIDC, runners, debugging |
| references/release-automation.md | Semantic versioning, semantic-release, changesets, goreleaser, changelog, publishing |
| references/testing-pipelines.md | Test stages, parallelism, coverage, service containers, e2e in CI, deployment pipelines |
tools
yt-dlp operations - the media ACQUISITION layer that feeds ffmpeg-ops: format selection (-S sort vs -f filters) that avoids post-download transcodes, --download-sections clip-at-download, audio-only extraction for STT pipelines (-x --audio-format opus), playlists + --download-archive incremental channel syncs, cookies/auth (--cookies-from-browser), rate limiting and politeness, SponsorBlock mark/remove, output templates (-o), subtitle download (--write-subs/--write-auto-subs), remux-vs-recode doctrine, and failure triage (403s, throttling, geo blocks, the nsig-extraction class that means yt-dlp is outdated). Triggers on: yt-dlp, ytdlp, youtube-dl, download video, download youtube, download from youtube, download playlist, download channel, archive channel, channel sync, rip audio, youtube to mp3, youtube to mp4, save video, grab video, video downloader, download subtitles, download transcript, clip from youtube, download section, sponsorblock, cookies-from-browser, download-archive, nsig, requested format is not available, sign in to confirm, download livestream, record stream, live-from-start, premiere, impersonate.
tools
Comprehensive ffmpeg/ffprobe operations - probe-first media processing: transcode and compress (H.264/H.265/AV1/Opus), frame-accurate cut/trim/concat, EDL-driven editing, color grading and .cube LUTs, audio loudnorm and mixing, STT/Whisper audio prep, subtitles, GIF and thumbnails, HLS packaging, hardware encoding (NVENC/QSV/AMF/VideoToolbox), restoration, scene and silence detection, VMAF quality gates, screen capture, yt-dlp interop. Triggers on: ffmpeg, ffprobe, transcode, convert video, compress video, encode video, extract audio, trim video, cut video, concat videos, video to gif, thumbnail, contact sheet, burn subtitles, watermark, resize video, crop video, change fps, slow motion, timelapse, loudnorm, normalize audio, audio for whisper, transcription prep, scene detection, silence detection, remove silence, color grade, LUT, tonemap HDR, vmaf, nvenc, hardware encode, hls, remux, faststart, deinterlace, stabilize video, denoise video, screen record, EDL, keyframes.
development
Payload CMS 3 (Next.js-native) architecture - collections, globals, fields, access control, hooks, Local API, storage adapters, and database (Postgres/MongoDB/SQLite). Use for: payload, payloadcms, payload cms, payload 3, collection config, access control, payload hooks, local api, payload fields, multi-tenant payload, payload nextjs, payload s3, payload r2, payloadcms architecture, headless cms typescript.
testing
Cypress end-to-end and component testing operations - selector/retry-ability strategy, cy.intercept network stubbing, cy.session auth, component vs e2e, flake diagnosis, CI, Test Replay. Use for: cypress, e2e test, component test, cy.get, cy.intercept, cy.session, data-cy, data-test, retry-ability, flake, flaky test, cypress.config, cy.mount, Test Replay, custom commands, fixtures.