GitHub Actions CI
Use this skill for GitHub Actions CI only.
Local skill: PR/push valid, required checks, workflow safety, caching, matrices, artifacts, workflow verification. Not for GitLab CI, Jenkins, ArgoCD, or deployment topology.
Boundary
Use this skill for:
- GitHub Actions workflow shape
- branch and event triggers for CI
- job dependencies and valid gates
- permissions, secrets, and trust boundaries inside GitHub Actions
- caching, matrices, artifacts, and CI performance
- workflow verification with
actionlint,act, and GitHub tooling
Pair with:
pythonwhen CI is mainlyuv,ruff,ty,rumdl, andpytestqualitywhen deciding test depth or verification gates that block mergessecuritywhen hardening secrets, provenance, or supply-chain controls
Out of scope:
- GitLab CI and Jenkins
- runtime deployment architecture
- Kubernetes rollout strategy
- release/deployment workflows not part of CI
Assets
assets/project/.github/workflows/ci.yml-- repo-shaped GitHub Actions CI workflow aligned with local Python toolchainassets/project/pyproject.toml-- matching project config used by CI workflowassets/project/src/myapp/main.py-- tiny Python entrypoint giving CI something real to lint and testassets/project/tests/test_main.py-- matching test module for example project
Core Principles
- fail fast: run cheap, high-signal checks early
- least privilege: declare explicit workflow permissions
- reproducible builds: lockfiles, pinned tools, deterministic install commands
- parallel by default: independent checks should not wait on each other
- short feedback loops: optimize PR feedback first
- auditable automation: make triggers, artifacts, required checks easy to understand
GitHub CI Workflow
- choose trigger surface:
pull_request,push, or both - set explicit
permissions - run lint, type, markdown, test gates before build jobs
- keep independent jobs parallel; aggregate only when needed
- upload only artifacts needed for later jobs or debugging
- verify workflow itself with CI-specific tooling
Trigger Strategy
Default CI triggers:
on:
pull_request:
push:
branches: [main, dev]
Rules:
- use
pull_requestfor merge-gating checks - use
pushfor protected branches that must stay green - add
workflow_dispatchonly for manual CI workflows needing operator input - avoid running expensive jobs on every branch unless truly required
For noisy repos, add concurrency control:
concurrency:
group: ci-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
Permissions
Always declare explicit permissions at workflow level; widen per job only when required.
permissions:
contents: read
Job-scoped widening examples:
security-events: writefor SARIF upload jobsid-token: writefor OIDC-backed verification or cloud-authenticated jobspull-requests: writeonly when workflow must comment on PRs
Avoid:
- implicit default permissions
- broad write access for all jobs
- exposing secrets to jobs that only lint or test
Default Python CI
Matches repo's current Python stack and local CI template.
name: CI
on:
pull_request:
push:
branches: [main, dev]
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
- run: uv sync --frozen
- run: uv run ruff format --check .
- run: uv run ruff check .
- run: uvx rumdl check .
- run: uv run ty check
- run: uv run pytest -v
Use templates/ci/github/ci.yml as repo-aligned starting point when scaffolding new workflow.
Validation Gates
Preferred order for Python repos:
- format check
- lint
- markdown lint
- type check
- test suite
Rules:
- keep build or artifact-producing jobs downstream of valid gates
- do not hide required failures behind
continue-on-error - use required status checks in branch protection for real blocking jobs
- if one stage is flaky, fix flake; do not downgrade gate casually
Parallelization and Job Shape
Independent checks should run in parallel:
jobs:
lint:
runs-on: ubuntu-latest
typecheck:
runs-on: ubuntu-latest
test:
runs-on: ubuntu-latest
build:
needs: [lint, typecheck, test]
runs-on: ubuntu-latest
Use one combined validate job when:
- setup cost dominates
- repo is small
- simpler branch protection matters more than per-check isolation
Split jobs when:
- checks are slow enough to benefit from parallelism
- failures should be easier to localize
- different runners, permissions, or artifacts needed
Matrix Builds
Use matrix only when compatibility is part of contract.
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
with:
python-version: ${{ matrix.python-version }}
- run: uv sync --frozen
- run: uv run pytest -v
Rules:
- keep matrix axes small and meaningful
- use
fail-fast: falseonly when collecting full compatibility data is valuable - do not add OS or Python-version matrices project does not support
Path Filters
Path filters reduce wasted CI in monorepos or split repos.
Simple trigger filtering:
on:
pull_request:
paths:
- "src/**"
- "tests/**"
- "pyproject.toml"
- "uv.lock"
Conditional job execution is better when different areas need different jobs.
Use path filters when:
- frontend and backend change independently
- docs-only changes should skip heavy test jobs
- package or service boundaries are already clear in repo
Caching
Cache only what materially improves CI time and stays reproducible.
Good candidates:
- dependency downloads keyed by lockfiles
- build caches such as
.pytest_cacheonly when proven helpful - Docker layers for image-building jobs
Rules:
- key caches from lockfiles or precise inputs
- prefer setup actions with built-in cache support when repo already uses them
- avoid caching entire virtual env unless proven stable
- treat stale caches as correctness risk, not only performance detail
Artifacts and Outputs
Upload only what later jobs or humans need.
Good artifacts:
- coverage reports
- test results
- built packages or wheels
- logs or debug bundles for failing workflows
Rules:
- never upload entire workspace
- keep retention short by default
- use artifacts to pass validated outputs forward, not as backup of everything
- prefer job outputs for small metadata and artifacts for actual files
Secrets and Trust Boundaries
- secrets should not be available to jobs that do not need them
- avoid exposing secrets to
pull_requestjobs from forks - prefer OIDC over long-lived static credentials when privileged jobs need identity
- keep privileged jobs off unreviewed branches
- pin third-party actions deliberately; review them like dependencies
Examples:
- CI lint/test workflow: usually
contents: readonly - SARIF upload workflow: add
security-events: write - OIDC-backed privileged job: add
id-token: writeonly to that job
Workflow Verification
Validate workflow itself before relying on it.
actionlint
act -n
gh workflow run ci.yml
gh run list --workflow ci.yml
Verification loop:
- lint workflow syntax with
actionlint - dry-run locally with
actwhen workflow is compatible - run workflow in GitHub when repo context matters
- inspect required permissions, artifact flow, and status checks
Anti-Patterns
- no explicit
permissions - secrets available in fork PR jobs
- serializing independent lint, type, and test jobs
continue-on-erroron required gates- no job timeouts
- uploading whole workspace as artifact
- caching unstable directories without lockfile-based keys
- one giant workflow mixing unprivileged valid with privileged jobs at same trust level
CI Checklist
- explicit triggers for
pull_requestand protected branchpush - explicit
permissions - deterministic install command such as
uv sync --frozen - format, lint, markdown, type, and test gates defined
- timeouts on long-running jobs
- matrices only when compatibility requires them
- path filters where they save real time
- artifacts limited to useful outputs
- workflow validated with
actionlint - branch protection wired to real required checks