> github-actions
Write and modify GitHub Actions workflows and custom actions. Covers CI/CD, workflow triggers, jobs, steps, secrets, runners, matrix builds, caching, artifacts, environments, and reusable workflows.
curl "https://skillshub.wtf/andrewneilson/github-actions-skill/SKILL.md?format=md"GitHub Actions Skill
This skill helps write and modify GitHub Actions workflows and custom actions.
Workflow Basics
Workflows are YAML files stored in .github/workflows/. They define automated processes triggered by events.
Minimal Workflow Structure
name: CI # Optional: Display name
on: push # Trigger event(s)
jobs:
build: # Job ID (must be unique)
runs-on: ubuntu-latest # Runner environment
steps:
- uses: actions/checkout@v5 # Use an action
- run: echo "Hello" # Run a command
Triggers (on:)
Common Events
on: push # Any push
on: [push, pull_request] # Multiple events
on:
push:
branches: [main, 'releases/**'] # Branch filter
paths: ['src/**'] # Path filter
pull_request:
types: [opened, synchronize] # Activity types
workflow_dispatch: # Manual trigger
inputs:
environment:
description: 'Deploy target'
required: true
type: choice
options: [dev, staging, prod]
schedule:
- cron: '0 0 * * *' # Daily at midnight UTC
Key Events Reference
| Event | Use Case |
|---|---|
push | Commits pushed to branches/tags |
pull_request | PR opened, updated, closed |
pull_request_target | PR from fork (runs in base context) |
workflow_dispatch | Manual trigger with inputs |
workflow_call | Reusable workflow |
schedule | Cron-based scheduling |
release | Release published/created |
workflow_run | After another workflow completes |
Jobs
Job Dependencies and Outputs
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.value }}
steps:
- id: version
run: echo "value=1.0.0" >> "$GITHUB_OUTPUT"
deploy:
needs: build # Depends on build job
runs-on: ubuntu-latest
steps:
- run: echo "Deploying ${{ needs.build.outputs.version }}"
Job Conditions
jobs:
deploy:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
Matrix Strategy
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [18, 20]
exclude:
- os: windows-latest
node: 18
include:
- os: ubuntu-latest
node: 20
coverage: true
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
Steps
Using Actions
steps:
- uses: actions/checkout@v5 # Public action (owner/repo@ref)
- uses: actions/checkout@8f4b7f84... # Pin to commit SHA (most secure)
- uses: ./.github/actions/my-action # Local action
- uses: docker://alpine:3.8 # Docker image
Running Commands
steps:
- run: npm install
- run: |
echo "Multi-line"
echo "commands"
shell: bash
working-directory: ./app
Step Conditions
steps:
- run: echo "Always runs"
if: always()
- run: echo "On failure"
if: failure()
- run: echo "Main branch only"
if: github.ref == 'refs/heads/main'
- run: echo "Not cancelled"
if: ${{ !cancelled() }}
Expressions and Contexts
Expression Syntax
Use ${{ expression }} to evaluate expressions. In if: conditions, ${{ }} is optional.
Common Contexts
| Context | Description |
|---|---|
github.* | Workflow run info (ref, sha, actor, repository) |
env.* | Environment variables |
vars.* | Repository/org variables |
secrets.* | Secrets |
steps.<id>.outputs.* | Step outputs |
needs.<job>.outputs.* | Job outputs |
matrix.* | Matrix values |
runner.* | Runner info (os, arch) |
Useful Expressions
# Context access
${{ github.event_name }}
${{ github.ref_name }}
${{ github.sha }}
${{ github.actor }}
# Conditionals
${{ github.ref == 'refs/heads/main' }}
${{ contains(github.event.head_commit.message, '[skip ci]') }}
${{ startsWith(github.ref, 'refs/tags/') }}
# Default values
${{ github.head_ref || github.run_id }}
# JSON manipulation
${{ toJSON(github.event) }}
${{ fromJSON(needs.job1.outputs.matrix) }}
Built-in Functions
| Function | Example |
|---|---|
contains(search, item) | contains(github.event.issue.labels.*.name, 'bug') |
startsWith(str, value) | startsWith(github.ref, 'refs/tags/') |
endsWith(str, value) | endsWith(github.repository, '-demo') |
format(str, ...) | format('Hello {0}', github.actor) |
join(array, sep) | join(matrix.os, ', ') |
toJSON(value) | toJSON(steps.test.outputs) |
fromJSON(str) | fromJSON(needs.setup.outputs.matrix) |
hashFiles(path) | hashFiles('**/package-lock.json') |
Secrets and Variables
Accessing Secrets
steps:
- run: echo "Token: $TOKEN"
env:
TOKEN: ${{ secrets.MY_TOKEN }}
- uses: some-action@v1
with:
api-key: ${{ secrets.API_KEY }}
Environment Variables
env: # Workflow-level
NODE_ENV: production
jobs:
build:
env: # Job-level
CI: true
steps:
- run: echo $MY_VAR
env: # Step-level
MY_VAR: value
Setting Outputs
steps:
- id: set-output
run: echo "result=success" >> "$GITHUB_OUTPUT"
- run: echo "${{ steps.set-output.outputs.result }}"
Runners
Common labels: ubuntu-latest, windows-latest, macos-latest. Self-hosted: runs-on: [self-hosted, linux, x64]. See references/RUNNERS.md for full specs.
Permissions
permissions:
contents: read
pull-requests: write
id-token: write # Required for OIDC
# Or disable all
permissions: {}
Concurrency
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Artifacts and Caching
Upload/Download Artifacts
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
- uses: actions/download-artifact@v4
with:
name: build-output
Dependency Caching
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
Environments
jobs:
deploy:
environment:
name: production
url: https://example.com
runs-on: ubuntu-latest
Reusable Workflows
Calling a Reusable Workflow
jobs:
call-workflow:
uses: owner/repo/.github/workflows/reusable.yml@main
with:
input1: value
secrets:
token: ${{ secrets.TOKEN }}
Creating a Reusable Workflow
on:
workflow_call:
inputs:
environment:
required: true
type: string
secrets:
token:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: echo "Deploying to ${{ inputs.environment }}"
env:
TOKEN: ${{ secrets.token }}
Common Patterns
Checkout and Setup
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
Conditional on File Changes
on:
push:
paths:
- 'src/**'
- '!src/**/*.md'
Run on Tag Push
on:
push:
tags:
- 'v*'
Workflow Commands Quick Reference
# Step outputs (use in later steps via steps.<id>.outputs.<name>)
echo "name=value" >> "$GITHUB_OUTPUT"
# Set environment variable for subsequent steps
echo "VAR_NAME=value" >> "$GITHUB_ENV"
# Job summary (supports markdown)
echo "### Build Results :rocket:" >> "$GITHUB_STEP_SUMMARY"
echo "| Test | Status |" >> "$GITHUB_STEP_SUMMARY"
echo "| --- | --- |" >> "$GITHUB_STEP_SUMMARY"
echo "| Unit | Passed |" >> "$GITHUB_STEP_SUMMARY"
# Add to PATH for subsequent steps
echo "/path/to/tool" >> "$GITHUB_PATH"
# Log annotations
echo "::error file=app.js,line=10::Something failed"
echo "::warning::Deprecation notice"
echo "::notice::FYI message"
# Mask a value from logs
echo "::add-mask::$SECRET_VALUE"
See references/WORKFLOW-COMMANDS.md for full details including ::group::, ::debug::, multiline values, and GITHUB_STATE.
Key Limits
| Limit | Value |
|---|---|
| Workflow run time | 35 days |
| Job execution time | 6 hours (self-hosted: unlimited) |
| Matrix combinations | 256 per workflow |
| Concurrent jobs (Free) | 20 (macOS: 5) |
| Concurrent jobs (Team/Enterprise) | 60 (macOS: 5) |
| Workflow file size | 512 KB |
| Caches total per repo | 10 GB |
| Artifact retention | 90 days (configurable) |
| Log retention | 400 days (configurable to 90) |
See references/LIMITS.md for full limits including API rates and storage quotas.
Troubleshooting
- Workflow not triggering: Check branch/path filters, verify file is in
.github/workflows/ - Permission denied: Add required
permissions:block - Secret not found: Verify secret name matches exactly (case-sensitive)
- Step output empty: Ensure using
>> "$GITHUB_OUTPUT"(not deprecated set-output) - Matrix job failing: Check
exclude/includesyntax and values - Debug logging: Re-run workflow with
ACTIONS_RUNNER_DEBUG: trueorACTIONS_STEP_DEBUG: true
References
Core:
references/WORKFLOW-SYNTAX.md- Full workflow YAML syntaxreferences/EXPRESSIONS.md- Contexts, functions, operatorsreferences/TRIGGERS.md- Event types and filteringreferences/WORKFLOW-COMMANDS.md- GITHUB_OUTPUT, GITHUB_ENV, annotations, job summariesreferences/LIMITS.md- Time limits, quotas, rate limits by plan
Build and deploy:
references/ACTIONS.md- Creating custom actions (JS, Docker, composite)references/RUNNERS.md- Runner specs, self-hosted configurationreferences/PATTERNS.md- Common workflow patternsreferences/LANGUAGE-CI.md- CI setup per language (Node, Python, Java, Go, Rust, Ruby, .NET, Swift)references/PUBLISHING.md- Publish to npm, PyPI, Docker Hub, GHCR, Maven Centralreferences/CLOUD-DEPLOYMENTS.md- Deploy to AWS, Azure, GCP with OIDC
Security and ops:
references/SECURITY.md- Secrets, OIDC (AWS/Azure/GCP), attestations, script injectionreferences/MIGRATION.md- Migrate from Jenkins, Travis CI, CircleCI, GitLab, Azure DevOpsreferences/ENTERPRISE.md- ARC, runner groups, larger runners, billing, private networking