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.

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- github-actions-workflows

Installs to ~/.agents/skills/github-actions-workflows/ for every project on this machine.

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.

~/.agents/skills/github-actions-workflows/SKILL.md

--- name: github-actions-workflows description: Designs GitHub Actions workflows - triggers and path filters, job graphs with needs and outputs, matrix strategies with include and exclude,… license: MIT ---

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

  1. Read AGENTS.md for the real build, lint and test commands; CI should run the same commands developers run, not a parallel set.
  2. List .github/workflows/*.yml, .github/actions/*/action.yml, and which checks are required by branch protection or rulesets (ask the user, or gh api repos/{owner}/{repo}/rules/branches/main).
  3. Look at recent runs: gh run list --limit 20 and gh run view <id> --log-failed (network and gh auth needed) to see durations and flaky steps.
  4. 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: read at the top; grant more per job only where needed.
  • concurrency cancels superseded PR runs but never cancels in-progress main runs.
  • timeout-minutes on every job; the default is 360 minutes of billed hang.
  • fail-fast: false on 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

NeedTrigger
PR checkspull_request (runs with read-only token for forks)
Post-merge build and deploypush on main or tags v*
Manual run with parametersworkflow_dispatch with typed inputs
Nightly jobsschedule cron (UTC; runs only on the default branch; delayed under load)
Chain after another workflowworkflow_run (runs in the base repo’s context; treat artifacts from it as untrusted)
Called by other workflowsworkflow_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

EcosystemDefault
Nodeactions/setup-node with cache: npm / pnpm / yarn (pnpm needs pnpm/action-setup first)
Pythonactions/setup-python with cache: pip, or astral-sh/setup-uv with enable-cache: true
Goactions/setup-go caches modules and build cache by default
Javaactions/setup-java with cache: gradle or maven
Docker layersdocker/build-push-action with cache-from: type=gha and cache-to: type=gha,mode=max
Anything elseactions/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=max Docker caches push out everything else.
  • Never cache node_modules across OS or Node versions; cache the package manager store. Deeper speed work (test sharding, affected-only builds, remote caches) is $ci-speedup.

Reusing logic

MechanismUse 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
MatrixSame 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

SymptomCheck
Workflow never startsYAML parse error (actionlint), trigger filters, workflow disabled, file not on the default branch for schedule/workflow_dispatch
Resource not accessible by integrationMissing permissions: scope for that job
Secrets emptyFork PRs do not receive secrets; environment secrets need the job’s environment:
Passes locally, fails in CIDifferent tool versions, missing services, case-sensitive paths on Linux, no TTY
Expression always trueif: ${{ 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.yamlgenerated from this page

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.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem