| name | github-workflows |
| description | Write, configure, and optimize GitHub Actions workflows including syntax, triggers, jobs, contexts, expressions, artifacts, and CI/CD patterns |
GitHub Workflows
Activate when creating, modifying, debugging, or optimizing GitHub Actions workflow files. This skill covers workflow syntax, structure, best practices, and common CI/CD patterns.
When to Use This Skill
Activate when:
- Writing .github/workflows/*.yml files
- Configuring workflow triggers and events
- Defining jobs, steps, and dependencies
- Using expressions and contexts
- Managing secrets and environment variables
- Implementing CI/CD pipelines
- Optimizing workflow performance
- Debugging workflow failures
Workflow File Structure
Basic Anatomy
name: CI # Workflow name (optional)
on: # Trigger events
push:
branches: [main, develop]
pull_request:
env: # Global environment variables
NODE_VERSION: '20'
jobs: # Job definitions
build:
name: Build and Test # Job name (optional)
runs-on: ubuntu-latest # Runner environment
steps:
- name: Checkout code # Step name (optional)
uses: actions/checkout@v4 # Use an action
- name: Run tests
run: npm test # Run command
File Location
Workflows must be in .github/workflows/ directory:
.github/
└── workflows/
├── ci.yml
├── deploy.yml
└── release.yml
Trigger Events (on:)
Push Events
on:
push:
branches:
- main
- 'release/**' # Glob patterns
tags:
- 'v*' # Version tags
paths:
- 'src/**' # Only when these paths change
- '!docs/**' # Ignore docs changes
Pull Request Events
on:
pull_request:
types:
- opened
- synchronize # New commits pushed
- reopened
branches:
- main
paths-ignore:
- '**.md'
Schedule (Cron)
on:
schedule:
# Every day at 2am UTC
- cron: '0 2 * * *'
# Every Monday at 9am UTC
- cron: '0 9 * * 1'
Manual Trigger (workflow_dispatch)
on:
workflow_dispatch:
inputs:
environment:
description: 'Deployment environment'
required: true
type: choice
options:
- development
- staging
- production
debug:
description: 'Enable debug logging'
required: false
type: boolean
default: false
Multiple Events
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
schedule:
- cron: '0 0 * * 0' # Weekly
Jobs
Basic Job Configuration
jobs:
build:
name: Build Application
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
Runner Selection
jobs:
test:
runs-on: ubuntu-latest # Ubuntu (fastest, most common)
test-macos:
runs-on: macos-latest # macOS
test-windows:
runs-on: windows-latest # Windows
test-specific:
runs-on: ubuntu-22.04 # Specific version
Matrix Strategy
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [18, 20, 21]
exclude:
- os: macos-latest
node: 18
fail-fast: false # Continue on failure
max-parallel: 4 # Concurrent jobs limit
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm test
Job Dependencies
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: npm run build
test:
needs: build # Wait for build
runs-on: ubuntu-latest
steps:
- run: npm test
deploy:
needs: [build, test] # Wait for multiple jobs
runs-on: ubuntu-latest
steps:
- run: npm run deploy
Conditional Execution
jobs:
deploy:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- run: npm run deploy
notify:
if: failure() # Run only if previous jobs failed
needs: [build, test]
runs-on: ubuntu-latest
steps:
- run: echo "Build failed"
Steps
Using Actions
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history
submodules: recursive # Include submodules
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
Running Commands
steps:
- name: Single command
run: npm install
- name: Multi-line script
run: |
echo "Installing dependencies"
npm ci
npm run build
- name: Shell selection
shell: bash
run: echo "Using bash"
Conditional Steps
steps:
- name: Run on main branch only
if: github.ref == 'refs/heads/main'
run: npm run deploy
- name: Run on PR only
if: github.event_name == 'pull_request'
run: npm run test:pr
Continue on Error
steps:
- name: Lint (optional)
continue-on-error: true
run: npm run lint
- name: Test (required)
run: npm test
Environment Variables and Secrets
Global Variables
env:
NODE_ENV: production
API_URL: https://api.example.com
jobs:
build:
runs-on: ubuntu-latest
steps:
- run: echo $NODE_ENV
Job-Level Variables
jobs:
build:
env:
BUILD_TYPE: release
steps:
- run: echo $BUILD_TYPE
Step-Level Variables
steps:
- name: Configure
env:
CONFIG_PATH: ./config.json
run: cat $CONFIG_PATH
Using Secrets
steps:
- name: Deploy
env:
API_KEY: ${{ secrets.API_KEY }}
DB_PASSWORD: ${{ secrets.DB_PASSWORD }}
run: ./deploy.sh
Setting Variables Between Steps
steps:
- name: Set version
id: version
run: echo "VERSION=$(cat version.txt)" >> $GITHUB_OUTPUT
- name: Use version
run: echo "Version is ${{ steps.version.outputs.VERSION }}"
Contexts
github Context
steps:
- name: Context information
run: |
echo "Repository: ${{ github.repository }}"
echo "Branch: ${{ github.ref_name }}"
echo "SHA: ${{ github.sha }}"
echo "Actor: ${{ github.actor }}"
echo "Event: ${{ github.event_name }}"
echo "Run ID: ${{ github.run_id }}"
env Context
env:
MY_VAR: value
steps:
- run: echo "${{ env.MY_VAR }}"
job Context
steps:
- name: Job status
if: job.status == 'success'
run: echo "Job succeeded"
steps Context
steps:
- id: first-step
run: echo "output=hello" >> $GITHUB_OUTPUT
- run: echo "${{ steps.first-step.outputs.output }}"
runner Context
steps:
- run: |
echo "OS: ${{ runner.os }}"
echo "Arch: ${{ runner.arch }}"
echo "Temp: ${{ runner.temp }}"
matrix Context
strategy:
matrix:
version: [18, 20]
steps:
- run: echo "Node ${{ matrix.version }}"
Expressions
Operators
steps:
# Comparison
- if: github.ref == 'refs/heads/main'
# Logical
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
- if: github.event_name == 'pull_request' || github.event_name == 'push'
# Negation
- if: "!cancelled()"
# Contains
- if: contains(github.event.head_commit.message, '[skip ci]')
# StartsWith/EndsWith
- if: startsWith(github.ref, 'refs/tags/v')
- if: endsWith(github.ref, '-beta')
Functions
steps:
# Status functions
- if: success() # Previous steps succeeded
- if: failure() # Any previous step failed
- if: always() # Always run
- if: cancelled() # Workflow cancelled
# String functions
- run: echo "${{ format('Hello {0}', github.actor) }}"
- if: contains(github.event.pull_request.labels.*.name, 'deploy')
# JSON functions
- run: echo '${{ toJSON(github.event) }}'
- run: echo '${{ fromJSON(env.CONFIG).database.host }}'
# Hash function
- run: echo "${{ hashFiles('**/package-lock.json') }}"
Artifacts
Upload Artifacts
steps:
- name: Build
run: npm run build
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: build-files
path: |
dist/
build/
retention-days: 7
if-no-files-found: error
Download Artifacts
jobs:
build:
steps:
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
test:
needs: build
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- run: npm test
Caching
npm Cache
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
Manual Cache
steps:
- uses: actions/cache@v4
with:
path: |
~/.npm
node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
Permissions
Repository Token Permissions
permissions:
contents: read # Repository content
pull-requests: write # PR comments
issues: write # Issue creation/comments
checks: write # Check runs
statuses: write # Commit statuses
deployments: write # Deployments
packages: write # Package registry
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Job-Level Permissions
jobs:
build:
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@v4
Concurrency
Prevent Concurrent Runs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true # Cancel running workflows
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: ./deploy.sh
Job-Level Concurrency
jobs:
deploy:
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false
steps:
- run: ./deploy.sh
Reusable Workflows
Define Reusable Workflow
# .github/workflows/reusable-test.yml
name: Reusable Test Workflow
on:
workflow_call:
inputs:
node-version:
required: true
type: string
coverage:
required: false
type: boolean
default: false
outputs:
test-result:
description: "Test execution result"
value: ${{ jobs.test.outputs.result }}
secrets:
token:
required: true
jobs:
test:
runs-on: ubuntu-latest
outputs:
result: ${{ steps.test.outputs.result }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm test
id: test
Call Reusable Workflow
jobs:
test:
uses: ./.github/workflows/reusable-test.yml
with:
node-version: '20'
coverage: true
secrets:
token: ${{ secrets.GITHUB_TOKEN }}
Common CI/CD Patterns
Node.js CI
name: Node.js CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 21]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm test
- run: npm run build
Docker Build and Push
name: Docker
on:
push:
branches: [main]
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Deploy on Release
name: Deploy
on:
release:
types: [published]
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: production
url: https://example.com
steps:
- uses: actions/checkout@v4
- name: Deploy to production
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
run: ./deploy.sh
Monorepo with Path Filtering
name: Monorepo CI
on:
pull_request:
paths:
- 'packages/**'
jobs:
detect-changes:
runs-on: ubuntu-latest
outputs:
frontend: ${{ steps.filter.outputs.frontend }}
backend: ${{ steps.filter.outputs.backend }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
frontend:
- 'packages/frontend/**'
backend:
- 'packages/backend/**'
test-frontend:
needs: detect-changes
if: needs.detect-changes.outputs.frontend == 'true'
runs-on: ubuntu-latest
steps:
- run: npm test --workspace=frontend
test-backend:
needs: detect-changes
if: needs.detect-changes.outputs.backend == 'true'
runs-on: ubuntu-latest
steps:
- run: npm test --workspace=backend
Debugging Workflows
Enable Debug Logging
Set repository secrets:
ACTIONS_RUNNER_DEBUG: trueACTIONS_STEP_DEBUG: true
Debug Steps
steps:
- name: Debug context
run: |
echo "Event: ${{ github.event_name }}"
echo "Ref: ${{ github.ref }}"
echo "SHA: ${{ github.sha }}"
echo "Actor: ${{ github.actor }}"
- name: Dump GitHub context
run: echo '${{ toJSON(github) }}'
- name: Dump runner context
run: echo '${{ toJSON(runner) }}'
Tmate Debugging
steps:
- name: Setup tmate session
if: failure()
uses: mxschmitt/action-tmate@v3
timeout-minutes: 30
Performance Optimization
Use Caching
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
Optimize Checkout
- uses: actions/checkout@v4
with:
fetch-depth: 1 # Shallow clone
sparse-checkout: | # Partial checkout
src/
tests/
Concurrent Jobs
jobs:
lint:
runs-on: ubuntu-latest
steps:
- run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- run: npm test
build:
needs: [lint, test] # Parallel lint and test
runs-on: ubuntu-latest
steps:
- run: npm run build
Anti-Fabrication Requirements
- Execute Read tool to verify workflow files exist before claiming structure
- Use Bash with
gh workflow listto confirm actual workflow names before referencing them - Execute
gh workflow view <workflow>to verify trigger configuration before documenting it - Use Glob to find actual workflow files before claiming their presence
- Execute
gh run listto verify actual workflow runs before discussing execution patterns - Never claim workflow success rates without actual run history analysis
- Validate YAML syntax using yamllint or similar tools via Bash before claiming correctness
- Report actual permission errors from workflow runs, not fabricated authorization issues
- Execute actual cache operations before claiming cache hit/miss percentages
- Use Read tool on action.yml files to verify action inputs/outputs before documenting usage