| name | github-actions-ci |
| description | Develop and troubleshoot GitHub Actions workflows and CI configurations. Use when creating workflows, debugging CI failures, understanding job logs, or optimizing CI pipelines. |
GitHub Actions CI Development
Guide for developing, debugging, and optimizing GitHub Actions workflows and CI configurations.
When to Use This Skill
- Creating new GitHub Actions workflows
- Debugging CI failures from job logs
- Understanding workflow syntax and features
- Optimizing CI performance
- Troubleshooting permission or environment issues
Workflow Structure
File Location
Workflows live in .github/workflows/ with .yml or .yaml extension.
Basic Structure
name: Workflow Name
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
job-name:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Step name
run: echo "Hello"
Debugging CI Failures
Step 1: Get the Job Log URL
When a PR check fails, the user typically provides a URL like:
https://github.com/owner/repo/actions/runs/12345/job/67890
Step 2: Fetch and Analyze Logs
Use GitHub CLI to get detailed logs:
gh run view <run-id> --log-failed
Or fetch specific job logs:
gh api repos/owner/repo/actions/jobs/<job-id>/logs
Step 3: Identify the Failure Point
Look for:
- Exit codes (non-zero indicates failure)
- Error messages in red/highlighted text
- The specific step that failed
- Environment or dependency issues
Step 4: Reproduce Locally
Always try to reproduce the failure locally before pushing fixes:
brew test-bot --only-tap-syntax
npm ci && npm test
<linter> --config <config-file> <files>
Common CI Patterns
Matrix Builds
Test across multiple OS/versions:
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
node: [18, 20]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
Conditional Steps
- name: Only on main
if: github.ref == 'refs/heads/main'
run: echo "On main branch"
- name: Only on PR
if: github.event_name == 'pull_request'
run: echo "This is a PR"
Caching Dependencies
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
Artifacts
Upload build outputs:
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
Homebrew-Specific CI
Homebrew Test Bot
The standard CI for Homebrew taps uses Homebrew/actions/build-bottle:
name: Test Formula
on:
pull_request:
paths:
- 'Formula/**'
jobs:
test-bot:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-22.04, macos-latest]
steps:
- uses: actions/checkout@v4
- uses: Homebrew/actions/setup-homebrew@master
- run: brew test-bot --only-tap-syntax
- run: brew test-bot --only-formulae
Common Homebrew CI Failures
| Failure | Cause | Solution |
|---|
brew style errors | Rubocop violations | Run brew test-bot --only-tap-syntax locally |
| Ruby in markdown | rubocop-md lints ruby code fences | Use text fence language instead |
| Formula audit errors | Missing fields or bad values | Run brew audit --new --formula <name> |
| Test failures | Test block issues | Verify test block creates needed files |
Troubleshooting Techniques
Check Workflow Syntax
yamllint .github/workflows/
actionlint .github/workflows/
View Recent Runs
gh run list --limit 10
gh run view <run-id>
gh run view <run-id> --log
Re-run Failed Jobs
gh run rerun <run-id> --failed
Check PR Status
gh pr view <pr-number> --json statusCheckRollup
Watch CI Progress
gh run watch <run-id>
Environment and Secrets
Using Secrets
env:
API_KEY: ${{ secrets.API_KEY }}
GitHub Token
The GITHUB_TOKEN is automatically available:
env:
GH_TOKEN: ${{ github.token }}
Environment Variables
env:
NODE_ENV: production
jobs:
build:
env:
CI: true
steps:
- env:
STEP_VAR: value
run: echo $STEP_VAR
Performance Optimization
Parallel Jobs
Jobs run in parallel by default. Use needs for dependencies:
jobs:
lint:
runs-on: ubuntu-latest
steps: [...]
test:
runs-on: ubuntu-latest
steps: [...]
deploy:
needs: [lint, test]
runs-on: ubuntu-latest
steps: [...]
Path Filtering
Only run on relevant changes:
on:
push:
paths:
- 'src/**'
- 'package.json'
paths-ignore:
- '**.md'
- 'docs/**'
Concurrency Control
Cancel redundant runs:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Debugging Checklist
Local Testing with act
nektos/act runs GitHub Actions locally using Docker, enabling fast iteration without pushing to GitHub.
Installation
brew install act
curl -s https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash
Basic Usage
act push
act pull_request
act -j build
act pull_request -e event.json
act -l
act -v
Secrets and Variables
act --secret-file .secrets
act -s GITHUB_TOKEN="$(gh auth token)"
act --env-file .env
Runner Images
act -P ubuntu-latest=catthehacker/ubuntu:act-latest
act -P ubuntu-latest=catthehacker/ubuntu:full-latest
act -P ubuntu-latest=node:16-buster-slim
Limitations
| Limitation | Workaround |
|---|
| Some actions don't work | Use -P to specify compatible runner images |
| No macOS/Windows runners | Test OS-specific code on actual GitHub runners |
| Service containers differ | May need Docker Compose for complex setups |
| GitHub context differences | Some github.* values unavailable locally |
| Large image downloads | Use micro images for simple workflows |
When to Use act
- Use
act: Rapid iteration, testing matrix logic, validating workflow syntax, testing secret handling
- Skip
act: OS-specific tests, actions requiring GitHub API, final validation before merge
Pre-commit Hooks for Workflows
Catch workflow errors before commit to reduce failed CI runs.
actionlint Hook
The most important hook - catches syntax errors, type mismatches, and common mistakes.
repos:
- repo: https://github.com/rhysd/actionlint
rev: v1.7.4
hooks:
- id: actionlint
files: ^\.github/workflows/
yamllint Hook
Validates YAML structure and formatting.
repos:
- repo: https://github.com/adrienverge/yamllint
rev: v1.35.1
hooks:
- id: yamllint
files: ^\.github/workflows/
args: [--config-file, .yamllint.yml]
Recommended .yamllint.yml for GitHub Actions:
extends: default
rules:
line-length:
max: 120
truthy:
check-keys: false
comments:
min-spaces-from-content: 1
check-yaml Hook
Basic YAML validity check.
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: check-yaml
files: ^\.github/workflows/
args: [--unsafe]
Complete Pre-commit Config
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
hooks:
- id: check-yaml
files: ^\.github/workflows/
args: [--unsafe]
- repo: https://github.com/adrienverge/yamllint
rev: v1.35.1
hooks:
- id: yamllint
files: ^\.github/workflows/
args: [-c, .yamllint.yml]
- repo: https://github.com/rhysd/actionlint
rev: v1.7.4
hooks:
- id: actionlint
Manual Validation
actionlint
actionlint .github/workflows/ci.yml
actionlint -shellcheck=$(which shellcheck)
Claude Hooks for Workflow Development
Configure Claude Code hooks to automatically validate workflows during development.
Post-Edit Hook: actionlint
Run actionlint after editing workflow files.
{
"hooks": {
"post_edit": [
{
"pattern": "^\\.github/workflows/.*\\.ya?ml$",
"command": "actionlint",
"description": "Lint GitHub Actions workflow"
}
]
}
}
Pre-Commit Hook: Full Validation
Validate before committing workflow changes.
{
"hooks": {
"pre_commit": [
{
"pattern": "^\\.github/workflows/.*\\.ya?ml$",
"command": "actionlint && yamllint .github/workflows/",
"description": "Validate GitHub Actions workflows"
}
]
}
}
Suggested Claude Workflow
- Edit workflow file
- Hook runs actionlint automatically
- Fix any reported issues
- Commit triggers pre-commit hooks
- Push with confidence - first CI run more likely to pass
Reusable Actions and Workflows
Action Source Priority
Always prefer actions from arustydev/gha for consistency and control.
Priority order:
arustydev/gha - First choice for all actions
- Third-party action - Temporary fallback with issue tracking
- Local development - When no suitable action exists
Using arustydev/gha Actions
steps:
- uses: arustydev/gha/setup-node@v1
- uses: arustydev/gha/deploy-preview@v1
When Action Not in arustydev/gha
If a needed action exists from a third party but not in arustydev/gha:
-
Create tracking issue:
gh issue create --repo arustydev/gha \
--title "[ACTION] Add <action-name>" \
--body "Third-party equivalent: <owner>/<action>@<version>
Currently using third-party version in: <project-name>
Requested functionality: <description>"
-
Use third-party temporarily:
steps:
- uses: third-party/action@v1
When No Suitable Action Exists
If no action (arustydev/gha or third-party) meets the need:
-
Create needs issue:
gh issue create --repo arustydev/gha \
--title "[ACTION] Need <action-name>" \
--body "## Use Case
<describe the need>
## Proposed Solution
<high-level approach>
## Initial Development
Will develop locally in: <project-name>"
-
Develop locally in the project:
.github/
└── actions/
└── my-action/
├── action.yml
├── package.json
├── tsconfig.json
└── src/
└── index.ts
-
Use TypeScript/Node for development:
name: My Action
description: Does something useful
inputs:
example:
description: Example input
required: true
runs:
using: node20
main: dist/index.js
import * as core from '@actions/core';
import * as github from '@actions/github';
async function run(): Promise<void> {
try {
const example = core.getInput('example', { required: true });
core.info(`Processing: ${example}`);
} catch (error) {
if (error instanceof Error) {
core.setFailed(error.message);
}
}
}
run();
-
Reference locally during development:
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/my-action
with:
example: value
-
When functional, open PR to arustydev/gha:
cp -r .github/actions/my-action ~/repos/gha/actions/
cd ~/repos/gha
git checkout -b feat/add-my-action
git add actions/my-action
git commit -m "feat(action): add my-action"
git push -u origin feat/add-my-action
gh pr create --title "feat(action): add my-action" \
--body "Closes #XX
Developed and tested in: <project-name>"
-
After merge, update the original project:
steps:
- uses: arustydev/gha/my-action@v1
with:
example: value
Remove the local .github/actions/my-action/ directory.
Reusable Workflows
For complex multi-job workflows, use reusable workflows:
name: Node CI
on:
workflow_call:
inputs:
node-version:
type: string
default: '20'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm ci && npm test
jobs:
ci:
uses: arustydev/gha/.github/workflows/node-ci.yml@main
with:
node-version: '20'
References