| name | github-actions |
| category | devops |
| description | GitHub Actions mastery โ workflow syntax, contexts, expressions, events, caching, security, reusable workflows, runners, limits. Based on official GitHub/Microsoft docs. |
| version | 1.0.0 |
| triggers | ["github actions","workflow yaml","ci cd github","action yml","workflow syntax","github workflow","action marketplace"] |
GitHub Actions โ Complete Reference Skill
All knowledge below is derived from official GitHub/Microsoft documentation (docs.github.com/en/actions) as of July 2026.
1. WORKFLOW FILE STRUCTURE
File: .github/workflows/<name>.yml (or .yaml)
name: Workflow Display Name
run-name: Run ${{ github.event_name }} by ${{ github.actor }}
on:
push:
branches: [main]
paths: ['src/**']
workflow_dispatch:
inputs:
environment:
type: choice
options: [dev, staging, prod]
permissions:
contents: read
packages: write
id-token: write
env:
NODE_VERSION: '20'
defaults:
run:
shell: bash
working-directory: ./app
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build and Test
runs-on: ubuntu-latest
timeout-minutes: 30
outputs:
version: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v4
- run: echo "hello"
2. TOP-LEVEL KEYS
| Key | Purpose |
|---|
name | Display name in Actions tab |
run-name | Dynamic run name (contexts: github, inputs, vars) |
on | Event triggers (REQUIRED) |
permissions | GITHUB_TOKEN scopes (18 available) |
env | Workflow-level env vars |
defaults | Default shell/working-directory for all jobs |
concurrency | Concurrency groups (group, cancel-in-progress, queue) |
3. TRIGGERS (on) โ COMPLETE
Simple triggers
on: push
on: [push, fork]
With activity types
on:
pull_request:
types: [opened, synchronize, reopened]
issues:
types: [opened, labeled]
Branch/tag filters (push)
on:
push:
branches: [main, 'releases/**']
branches-ignore: ['**-alpha']
tags: ['v*']
tags-ignore: ['v0.*']
Path filters
on:
push:
paths: ['src/**', '*.json']
paths-ignore: ['docs/**']
- Path + branch filters: BOTH must match
! prefix for exclusion: paths: ['src/**', '!src/docs/**']
- Order matters for
! patterns
Glob pattern cheat sheet
| Pattern | Meaning |
|---|
* | Any except / |
** | Any including / |
? | Zero or one |
+ | One or more |
[abc] | Character class |
! | Negation (first char only) |
Scheduled (cron)
on:
schedule:
- cron: '0 0 * * *'
- cron: '0 12 * * 1-5'
POSIX cron: * (any), , (list), - (range), / (step)
Supports timezone: cron: { expression: '0 9 * * 1', timezone: 'Asia/Tokyo' }
Manual dispatch
on:
workflow_dispatch:
inputs:
debug:
type: boolean
default: false
environment:
type: choice
options: [dev, prod]
version:
type: string
required: true
count:
type: number
default: 1
Reusable workflow call
on:
workflow_call:
inputs:
config-path:
required: true
type: string
outputs:
result:
value: ${{ jobs.build.outputs.result }}
secrets:
token:
required: true
Triggered by another workflow
on:
workflow_run:
workflows: ["Build"]
types: [completed]
branches: [main]
Repository dispatch
on:
repository_dispatch:
types: [deploy-command]
4. EVENTS โ ALL 34 TYPES
| Event | Activity Types | Use Case |
|---|
push | โ | Branch/tag push |
pull_request | opened, synchronize, reopened (+ 17 more) | PR activity |
pull_request_target | same as pull_request | PR from privileged context (DANGEROUS) |
pull_request_review | submitted, edited, dismissed | PR review |
pull_request_review_comment | created, edited, deleted | PR review comment |
schedule | โ | Cron-based |
workflow_dispatch | โ | Manual trigger |
workflow_call | โ | Reusable workflow |
workflow_run | completed, requested, in_progress | Chained workflows |
repository_dispatch | custom types | External API trigger |
issues | opened, edited, closed, labeled, etc. | Issue activity |
issue_comment | created, edited, deleted | Issue/PR comment |
label | created, edited, deleted | Label activity |
milestone | created, closed, opened, edited, deleted | Milestone activity |
release | published, unpublished, created, edited, deleted, prereleased, released | Release activity |
fork | โ | Repository forked |
watch | started | Star event |
create | โ | Branch/tag created |
delete | โ | Branch/tag deleted |
deployment | โ | Deployment created |
deployment_status | โ | Deployment status change |
discussion | created, edited, answered, etc. | Discussion activity |
discussion_comment | created, edited, deleted | Discussion comment |
page_build | โ | GitHub Pages build |
public | โ | Repo made public |
gollum | โ | Wiki create/update |
check_run | created, rerequested, completed | Check run activity |
check_suite | completed | Check suite activity |
status | โ | Commit status change |
merge_group | checks_requested | Merge queue |
registry_package | published, updated | Container/Package registry |
branch_protection_rule | created, edited, deleted | Branch protection change |
image_version | names + versions | Custom runner image |
Key events detailed:
pull_request โ GITHUB_SHA = last merge commit, GITHUB_REF = refs/pull/N/merge
- Only runs for opened/synchronize/reopened by default
- Does NOT run on merge conflicts (use
pull_request_target if needed)
- Use
github.event.pull_request.head.sha for actual head commit
pull_request_target โ GITHUB_SHA = last commit on base branch, GITHUB_REF = base branch
- Runs in context of BASE repo (has write access)
- SECURITY RISK: Never checkout untrusted PR code
- Runs even with merge conflicts
issue_comment โ fires for BOTH issues and PRs
- Distinguish:
if: ${{ github.event.issue.pull_request }}
- Only runs workflow from default branch
workflow_run โ can access secrets and write tokens
- Max 3 levels of chaining
- Max 3 workflows in chain
5. CONTEXTS โ ALL 12
github (30+ properties)
| Property | Description |
|---|
github.actor | Username of triggerer |
github.ref | Full ref (refs/heads/main) |
github.ref_name | Short ref (main) |
github.ref_type | branch or tag |
github.sha | Triggering commit SHA |
github.event_name | Event name (push, etc.) |
github.event | Full webhook payload |
github.repository | owner/repo |
github.repository_owner | owner username |
github.workflow | Workflow name |
github.run_id | Unique run number |
github.run_number | Incrementing per workflow |
github.run_attempt | Attempt number |
github.server_url | https://github.com |
github.api_url | https://api.github.com |
github.workspace | Runner working dir |
github.token | GITHUB_TOKEN |
github.head_ref | PR source branch |
github.base_ref | PR target branch |
github.event.issue.number | Issue/PR number |
github.event.pull_request.head.sha | PR head SHA |
github.event.release.tag_name | Release tag |
env โ Custom env vars (step > job > workflow precedence)
vars โ Config variables (org/repo/environment levels)
job โ Current job info (status, container, services)
jobs โ Reusable workflow job outputs only
steps โ Previous step outputs, conclusion, outcome
conclusion = result after continue-on-error
outcome = result before continue-on-error
runner โ os, arch, temp, tool_cache, name, debug, environment
secrets โ Available secrets (not in composite actions)
strategy โ fail-fast, job-index, job-total, max-parallel
matrix โ Current matrix values
needs โ Dependency job outputs and results
inputs โ workflow_dispatch/workflow_call inputs
Context availability varies by workflow key
on:, workflow-level env: github, secrets, inputs, vars
jobs.<id>.if: github, needs, vars, inputs (+ always/cancelled/success/failure functions)
steps.*: ALL 12 contexts + hashFiles function
6. EXPRESSIONS
Operators
| Op | Meaning |
|---|
() | Grouping |
[] | Index |
. | Property |
! | Not |
<, <=, >, >= | Comparison |
==, != | Equality |
&&, || | Logical |
Type casting
- Strings to number:
'' โ 0, '1' โ 1, 'abc' โ 0 (NaNโ0)
- Strings to boolean:
'' โ false, 'true' โ true, 'false' โ true
Functions
| Function | Example |
|---|
contains(search, item) | contains('hello', 'll') โ true |
startsWith(search, value) | startsWith('hello', 'he') โ true |
endsWith(search, value) | endsWith('hello', 'lo') โ true |
format(str, vals...) | format('{0} {1}', 'hi', 'world') |
join(arr, sep?) | join(['a','b'], '-') โ 'a-b' |
toJSON(value) | Serialize to JSON string |
fromJSON(value) | Parse JSON string |
hashFiles(path) | Hash matching files (glob patterns) |
case(pred1,val1,..., default) | Switch-like conditional |
Status check functions
| Function | Meaning |
|---|
success() | All previous steps succeeded (default) |
failure() | Any previous step failed |
always() | Always run regardless |
cancelled() | Workflow was cancelled |
skipped() | Previous step was skipped |
Object filter syntax
if: ${{ contains(github.event.pull_request.labels.*.name, 'bug') }}
* iterates array/object values
7. JOBS
jobs:
build:
name: Build
runs-on: ubuntu-latest
needs: [test]
if: ${{ success() && github.ref == 'refs/heads/main' }}
timeout-minutes: 30
permissions:
contents: read
env:
NODE_ENV: production
defaults:
run:
shell: bash
concurrency:
group: build-${{ github.ref }}
cancel-in-progress: true
outputs:
version: ${{ steps.version.outputs.value }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node: [18, 20, 22]
exclude:
- os: macos-latest
node: 18
include:
- os: ubuntu-latest
node: 20
experimental: true
fail-fast: true
max-parallel: 2
container:
image: node:20
credentials:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
env:
NODE_ENV: test
ports: ['3000:3000']
volumes: ['/tmp:/tmp']
services:
redis:
image: redis:7
ports: ['6379:6379']
postgres:
image: postgres:16
env:
POSTGRES_PASSWORD: test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
Runner selection
runs-on: ubuntu-latest
runs-on: self-hosted
runs-on: [self-hosted, linux, gpu]
runs-on: ubuntu-latest-4-cores
Conditional execution
if: ${{ github.event_name == 'push' }}
if: ${{ needs.build.result == 'success' }}
if: ${{ always() }}
if: ${{ !cancelled() && needs.build.result == 'success' }}
Matrix with variables
strategy:
matrix:
include:
- os: ubuntu-latest
shard: 1
total: 3
- os: ubuntu-latest
shard: 2
total: 3
8. STEPS
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4
with:
repository: owner/repo
ref: v1
token: ${{ secrets.GITHUB_TOKEN }}
submodules: true
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: 'npm'
- name: Build
run: npm run build
env:
API_KEY: ${{ secrets.API_KEY }}
working-directory: ./app
shell: bash
- name: Test
id: test
run: |
echo "result=pass" >> "$GITHUB_OUTPUT"
continue-on-error: true
timeout-minutes: 10
if: ${{ steps.test.outcome == 'failure' }}
- name: Background server
run: ./server
background: true
wait: true
wait-all: true
cancel: true
- name: Parallel steps
parallel:
- name: Lint
run: npm run lint
- name: Type check
run: npm run typecheck
Action references
uses: actions/checkout@v4
uses: actions/cache@v4
uses: owner/repo/.github/workflows/ci.yml@main
uses: ./my-local-action
uses: docker://nginx:latest
Setting outputs
- id: my-step
run: |
echo "color=blue" >> "$GITHUB_OUTPUT"
# Multiline:
echo "data<<EOF" >> "$GITHUB_OUTPUT"
echo "line1" >> "$GITHUB_OUTPUT"
echo "line2" >> "$GITHUB_OUTPUT"
echo "EOF" >> "$GITHUB_OUTPUT"
9. ENVIRONMENT FILES & WORKFLOW COMMANDS
| File | Variable | Purpose |
|---|
$GITHUB_ENV | โ | Set env vars for subsequent steps |
$GITHUB_OUTPUT | โ | Set step outputs |
$GITHUB_PATH | โ | Add to PATH |
$GITHUB_STEP_SUMMARY | โ | Job summary markdown |
$GITHUB_STATE | โ | Pass data between pre/main/post |
Environment variables
echo "MY_VAR=hello" >> "$GITHUB_ENV"
echo "MULTI<<EOF" >> "$GITHUB_ENV"
echo "line1" >> "$GITHUB_ENV"
echo "EOF" >> "$GITHUB_ENV"
Annotations
echo "::error file=app.js,line=1::Missing semicolon"
echo "::warning title=Lint::Deprecated API usage"
echo "::notice file=test.js,line=42::Performance issue"
echo "::debug::Debug message"
Log grouping
echo "::group::My section"
echo "details here"
echo "::endgroup::"
Masking secrets
echo "::add-mask::$MY_SECRET"
Stop/resume command processing
echo "::stop-commands::$(uuidgen)"
echo "::$(uuidgen)::"
Job summary
echo "### Build Result ๐" >> "$GITHUB_STEP_SUMMARY"
echo "| Metric | Value |" >> "$GITHUB_STEP_SUMMARY"
echo "|--------|-------|" >> "$GITHUB_STEP_SUMMARY"
echo "| Tests | 142 |" >> "$GITHUB_STEP_SUMMARY"
10. REUSABLE WORKFLOWS
Caller
jobs:
call-build:
uses: owner/repo/.github/workflows/build.yml@v2
with:
config: ./config.yml
secrets:
inherit
token: ${{ secrets.PERSONAL_TOKEN }}
Callee definition
name: Build Workflow
on:
workflow_call:
inputs:
config:
required: true
type: string
debug:
type: boolean
default: false
outputs:
version:
value: ${{ jobs.build.outputs.version }}
secrets:
token:
required: true
jobs:
build:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.value }}
steps:
- id: version
run: echo "version=1.0.0" >> "$GITHUB_OUTPUT"
Restrictions
- Max 10 levels of nesting (caller + 9 reusable)
- Max 25 unique reusable workflows per run
- Permissions can only be maintained or reduced, never elevated
- Secrets only pass to directly called workflow (not transitive unless explicitly passed)
- No recursive loops allowed
11. DEPENDENCY CACHING
actions/cache
- uses: actions/cache@v4
with:
path: |
~/.npm
node_modules
key: npm-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
npm-${{ runner.os }}-
setup-* with built-in cache
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- uses: actions/setup-python@v5
with:
python-version: '3.12'
cache: 'pip'
Cache limits
- 10 GB default per repository
- 7 days eviction (no access)
- 200 uploads/min, 1500 downloads/min per repo
- Eviction: oldest-accessed-first
Cache keys strategy
- Primary: exact lock file hash
- Restore keys: OS prefix (partial match falls back)
- New cache created on restore-key miss
12. GITHUB-HOSTED RUNNERS
| Runner | OS | CPU | RAM | SSD |
|---|
ubuntu-latest (24.04) | Linux x64 | 4 | 16 GB | 14 GB |
ubuntu-latest arm64 | Linux arm64 | 4 | 16 GB | 14 GB |
ubuntu-slim | Linux x64 | 1 | 5 GB | 14 GB |
windows-latest | Windows x64 | 4 | 16 GB | 14 GB |
macos-15 | macOS ARM64 (M1) | 3 | 7 GB | 14 GB |
macos-14 | macOS ARM64 (M1) | 3 | 7 GB | 14 GB |
Job execution limits
- GitHub-hosted: 6 hours max
- Self-hosted: 5 days max
- Matrix: max 256 jobs per workflow
- Concurrency by plan: Free=20, Pro=40, Team=60, Enterprise=500
Artifacts
- Free: 500 MB storage, 2000 min upload
- Pro: 1 GB, 3000 min
- Team: 2 GB, 3000 min
- Enterprise: 50 GB, 50000 min
13. SECURITY BEST PRACTICES
Script injection prevention
- run: echo "${{ github.event.issue.title }}"
- env:
TITLE: ${{ github.event.issue.title }}
run: echo "$TITLE"
Pin actions to SHA
uses: actions/checkout@v4
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
Secrets management
- Use least privilege for GITHUB_TOKEN
- Mask sensitive data with
::add-mask::
- Never put secrets in commit messages or PR titles
- Rotate exposed secrets immediately
- Don't use structured data (JSON) as secrets
Privileged workflows
pull_request_target has write access โ NEVER checkout untrusted PR code
workflow_run can access secrets โ useful for privilege separation
- Use OIDC for cloud provider authentication (no static credentials)
Self-hosted runners
- Don't use for public repos (fork PRs could execute code)
- Use ephemeral/JIT runners when possible
- Organize into runner groups with limited access
14. WORKFLOW CANCELLATION
When a workflow is cancelled:
- Server re-evaluates
if conditions โ always() keeps running
- Cancellation message sent to runners
- Steps re-evaluated with
if
- Signal escalation: SIGINT โ 7.5s โ SIGTERM โ 2.5s โ kill
- 5-minute forcible timeout for all remaining
15. CONCURRENCY
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref == 'refs/heads/main' }}
queue:
max: 5
cancel-in-progress: true โ cancels running when new queued
- Use branch-specific groups to avoid cancelling unrelated runs
16. COMMON PATTERNS
Matrix build
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [18, 20]
fail-fast: false
Conditional job execution
deploy:
needs: [test, build]
if: ${{ always() && needs.test.result == 'success' && needs.build.result == 'success' }}
Environment protection rules
deploy:
environment:
name: production
url: https://myapp.com
Composite action (action.yml)
name: My Action
description: Does things
inputs:
name:
required: true
outputs:
result:
value: ${{ steps.step1.outputs.value }}
runs:
using: composite
steps:
- id: step1
run: echo "result=done" >> "$GITHUB_OUTPUT"
shell: bash
Skip CI
git commit -m "chore: update docs [skip ci]"
git commit -m "docs: update [no ci]"
on:
push:
paths-ignore:
- '**.md'
Re-run specific jobs
- name: Retry on failure
run: |
for i in 1 2 3; do
command && break || sleep 10
done
17. DEFAULT ENVIRONMENT VARIABLES
| Variable | Description |
|---|
CI | Always true |
GITHUB_ACTION | Current action/step name |
GITHUB_ACTION_PATH | Action location (composite only) |
GITHUB_ACTIONS | true when Actions running |
GITHUB_ACTOR | Triggering username |
GITHUB_API_URL | https://api.github.com |
GITHUB_BASE_REF | PR target branch |
GITHUB_ENV | Path to env file |
GITHUB_EVENT_NAME | Event name |
GITHUB_EVENT_PATH | Full event payload path |
GITHUB_HEAD_REF | PR source branch |
GITHUB_JOB | Current job_id |
GITHUB_OUTPUT | Path to output file |
GITHUB_PATH | Path to PATH file |
GITHUB_REF | Full ref |
GITHUB_REF_NAME | Short ref name |
GITHUB_REPOSITORY | owner/repo |
GITHUB_RUN_ID | Unique run ID |
GITHUB_RUN_NUMBER | Incrementing run number |
GITHUB_SHA | Triggering commit |
GITHUB_WORKFLOW | Workflow name |
GITHUB_WORKSPACE | Runner workdir |
RUNNER_OS | Linux/Windows/macOS |
RUNNER_ARCH | X86/X64/ARM/ARM64 |
RUNNER_TEMP | Temp directory |
RUNNER_TOOL_CACHE | Preinstalled tools path |
18. LIMITS
| Limit | Value |
|---|
| Workflow run time | 35 days |
| Job execution (GH-hosted) | 6 hours |
| Job execution (self-hosted) | 5 days |
| Queue time | 24 hours |
| Matrix jobs | 256 per workflow |
| Re-runs per workflow | 50 |
| Event rate | 1500/10s/repo |
| Reusable workflow nesting | 10 levels |
| Max workflow file size | 1 MB |
Step run: size | 21,000 chars |
Step timeout-minutes | Max 360 |
| Background steps | Max 10 concurrent |
| Job outputs | Max 1 MB per job, 50 MB per workflow |
| Config variables | 48 KB each |
| Repo variables | 500 vars, 256 KB combined with org |
| Cache per repo | 10 GB default |
19. GITHUB_TOKEN PERMISSIONS (18 scopes)
actions, attestations, checks, code-quality, contents, deployments, discussions, id-token, issues, models, packages, pages, pull-requests, security-events, statuses, vulnerability-alerts, metadata (read-only, always granted), artifact-metadata
permissions:
contents: read
issues: write
pull-requests: write
id-token: write
Least privilege: set permissions at workflow or job level. Default is permissive for GITHUB_TOKEN.