GitHub Actions Workflows skill for Codex
Fast PR signal, shared pipelines, guarded deploys. Codex designs .github/workflows files around the commands in AGENTS.md: lint and test jobs with fail-fast off, lockfile-keyed caches, concurrency groups that cancel stale PR runs but never main, and timeouts on every job. Repeated YAML becomes reusable workflows or composite actions, path filters never strand required checks, deploys go through protected environments, and actionlint checks everything first.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- github-actions-workflows Installs to ~/.agents/skills/github-actions-workflows/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install github-actions-workflows The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project github-actions-workflows Installs to .agents/skills/github-actions-workflows/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/github-actions-workflows.zipunzip -o github-actions-workflows.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) github-actions-workflows PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $github-actions-workflows, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Designs GitHub Actions workflows - triggers and path filters, job graphs with needs and outputs, matrix strategies with include and exclude, dependency caching through setup actions and actions/cache, reusable workflows with workflow_call, composite actions, concurrency groups, artifacts, environments for deploys, timeouts, and actionlint checks. Use when creating or restructuring CI/CD in .github/workflows, deduplicating YAML across repos, adding a build matrix, fixing cache misses, cancelling superseded runs, or debugging a workflow that does not trigger or fails only in Actions.
Use it when
- A repository needs CI or CD in GitHub Actions
- Workflows are duplicated across jobs or repositories
- Caches never hit or old runs keep running after new pushes
- A workflow does not trigger or behaves differently than local builds
Use something else when
- The concern is token permissions, OIDC, action pinning or pull_request_target: use github-actions-security
- CI works but is too slow and needs sharding or affected-only builds: use ci-speedup
How to check the result
actionlint reports no errors, a draft PR run passes with every required check reporting, and a second run shows dependency cache hits.
The mistake it prevents
Adding paths filters to a workflow that provides a required status check, so pull requests touching other paths wait forever.
A good workflow gives a clear red or green signal on every pull request in minutes, runs
each expensive step once, cancels runs that a newer push made obsolete, shares logic
through reusable workflows instead of copy-paste, and keeps deploys behind protected
environments. Security hardening (token scopes, OIDC, pinning by SHA, untrusted
triggers) is covered in depth by $github-actions-security; apply its basics here too.
Inspect first
- Read
AGENTS.mdfor the real build, lint and test commands; CI should run the same commands developers run, not a parallel set. - List
.github/workflows/*.yml,.github/actions/*/action.yml, and which checks are required by branch protection or rulesets (ask the user, orgh api repos/{owner}/{repo}/rules/branches/main). - Look at recent runs:
gh run list --limit 20andgh run view <id> --log-failed(network andgh authneeded) to see durations and flaky steps. - Note runners in use (
ubuntu-latest, larger runners, self-hosted labels) and any organisation-level reusable workflows already available.
Baseline CI workflow
name: ci
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ci-${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
lint:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version-file: .nvmrc
cache: npm
- run: npm ci
- run: npm run lint
test:
runs-on: ${{ matrix.os }}
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest]
node: [20, 22, 24]
include:
- os: windows-latest
node: 22
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}
cache: npm
- run: npm ci
- run: npm test -- --reporter=junit --outputFile=reports/junit.xml
- uses: actions/upload-artifact@v6
if: failure()
with:
name: test-reports-${{ matrix.os }}-${{ matrix.node }}
path: reports/
retention-days: 7
build:
needs: [lint, test]
runs-on: ubuntu-latest
timeout-minutes: 15
outputs:
version: ${{ steps.meta.outputs.version }}
steps:
- uses: actions/checkout@v6
- id: meta
run: echo "version=$(node -p "require('./package.json').version")-${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- run: echo "Built ${{ steps.meta.outputs.version }}" >> "$GITHUB_STEP_SUMMARY"
Why each piece:
permissions: contents: readat the top; grant more per job only where needed.concurrencycancels superseded PR runs but never cancels in-progressmainruns.timeout-minuteson every job; the default is 360 minutes of billed hang.fail-fast: falseon test matrices so one failing cell does not hide the others.- Upload artifacts only on failure (or for build outputs), with short retention.
Pin actions to a full commit SHA for third-party actions (uses: owner/action@<sha> # v1.2.3)
and let Dependabot or Renovate update them; first-party actions/* majors are acceptable
in many orgs, but follow the repo’s policy.
Triggers
| Need | Trigger |
|---|---|
| PR checks | pull_request (runs with read-only token for forks) |
| Post-merge build and deploy | push on main or tags v* |
| Manual run with parameters | workflow_dispatch with typed inputs |
| Nightly jobs | schedule cron (UTC; runs only on the default branch; delayed under load) |
| Chain after another workflow | workflow_run (runs in the base repo’s context; treat artifacts from it as untrusted) |
| Called by other workflows | workflow_call |
Avoid pull_request_target unless you have read $github-actions-security; checking out
PR code under it hands secrets to forks.
Path filters (paths:, paths-ignore:) skip the whole workflow. If that workflow
provides a required check, the PR waits forever for a check that never reports. For
required checks, keep the workflow running and skip inside it (a changes job using
git diff or a paths-filter action, with later jobs conditioned on its output), or use a
final always-running aggregate job as the required check:
ci-ok:
if: always()
needs: [lint, test, build]
runs-on: ubuntu-latest
steps:
- run: |
[[ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "false" ]]
Caching
| Ecosystem | Default |
|---|---|
| Node | actions/setup-node with cache: npm / pnpm / yarn (pnpm needs pnpm/action-setup first) |
| Python | actions/setup-python with cache: pip, or astral-sh/setup-uv with enable-cache: true |
| Go | actions/setup-go caches modules and build cache by default |
| Java | actions/setup-java with cache: gradle or maven |
| Docker layers | docker/build-push-action with cache-from: type=gha and cache-to: type=gha,mode=max |
| Anything else | actions/cache@v5 keyed on a lockfile hash |
- uses: actions/cache@v5
with:
path: ~/.cache/pre-commit
key: pre-commit-${{ runner.os }}-${{ hashFiles('.pre-commit-config.yaml') }}
- Key on lockfile hashes, not on
github.sha; a per-commit key never hits. - Caches are scoped by branch: PRs can read caches from the base branch, not from other
PRs. Warm caches with runs on
main. - Repository cache storage is limited (10 GB by default) and least recently used entries
are evicted; huge
mode=maxDocker caches push out everything else. - Never cache
node_modulesacross OS or Node versions; cache the package manager store. Deeper speed work (test sharding, affected-only builds, remote caches) is$ci-speedup.
Reusing logic
| Mechanism | Use for |
|---|---|
Reusable workflow (on: workflow_call) | Whole jobs: a standard build-test pipeline shared by many repos |
Composite action (.github/actions/x/action.yml) | A sequence of steps used inside jobs: setup plus install |
| Matrix | Same job, different parameters |
# .github/workflows/node-ci.yml (callee)
on:
workflow_call:
inputs:
node-version:
type: string
default: "22"
secrets:
NPM_TOKEN:
required: false
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with: { node-version: "${{ inputs.node-version }}", cache: npm }
- run: npm ci && npm test
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
# caller
jobs:
ci:
uses: acme/workflows/.github/workflows/node-ci.yml@v3
with:
node-version: "24"
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Pass secrets explicitly; secrets: inherit hands every secret to the callee. Reference
shared workflows by tag or SHA, not @main.
Deploy jobs
deploy:
needs: build
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment:
name: production
url: https://app.example.com
concurrency:
group: deploy-production
cancel-in-progress: false
permissions:
contents: read
id-token: write # OIDC to the cloud provider instead of stored keys
steps:
- run: ./scripts/deploy.sh "${{ needs.build.outputs.version }}"
The production environment should have required reviewers and branch restrictions
configured in repository settings. Do not enable or trigger production deploys, or
create tags and releases that trigger them, without the user’s confirmation.
Debugging
| Symptom | Check |
|---|---|
| Workflow never starts | YAML parse error (actionlint), trigger filters, workflow disabled, file not on the default branch for schedule/workflow_dispatch |
Resource not accessible by integration | Missing permissions: scope for that job |
| Secrets empty | Fork PRs do not receive secrets; environment secrets need the job’s environment: |
| Passes locally, fails in CI | Different tool versions, missing services, case-sensitive paths on Linux, no TTY |
| Expression always true | if: ${{ inputs.flag }} with a string 'false'; compare explicitly |
Re-run with debug logging by setting the repository variable ACTIONS_STEP_DEBUG to true,
or gh run rerun <id> --debug.
Workflow
- [ ] 1. Read AGENTS.md commands, existing workflows, required checks
- [ ] 2. Draft jobs: lint, test (matrix), build, optional deploy
- [ ] 3. Add permissions, concurrency, timeouts, caching
- [ ] 4. Extract repeated jobs into reusable workflows or composite actions
- [ ] 5. actionlint locally; push to a branch and open a draft PR
- [ ] 6. Confirm the run: cache hit on second run, durations, required checks reporting
Verification
actionlint # static check of all workflows (shellcheck-aware)
gh workflow list
gh run watch # follow the run you triggered
gh run view <id> --log-failed
gh cache list --limit 20 # keys and sizes after a run
Pushing branches and opening PRs is visible to others: confirm before doing it. A second run on the same lockfile should show cache hits in the setup step logs.
Output
**Workflows:** ci.yml (lint, test matrix 3x Node + Windows, build, ci-ok aggregate), deploy.yml
**Reuse:** node-ci reusable workflow at acme/workflows@v3; composite action setup-app
**Speed:** PR wall time 14 min -> 6 min; npm cache hit on second run
**Safety:** contents: read default, deploy only via production environment with reviewers
**Checks:** actionlint clean; required check switched to ci-ok (needs admin change in rulesets) What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "GitHub Actions Workflows"
short_description: "Fast PR signal, shared pipelines, guarded deploys"
default_prompt: "$github-actions-workflows Set up GitHub Actions CI for this repo with a Node version matrix" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.