---
name: "ci_pipelines"
description: "Designs GitHub Actions CI workflows (.github/workflows/*.yml): triggers, jobs and steps, matrix builds, dependency caching, secrets, and concurrency groups that cancel redundant runs."
---

# CI Pipelines

## Purpose
Ship reliable CI on GitHub Actions: a workflow file that runs tests on every push and PR, caches dependencies, consumes secrets safely, and cancels stale runs so queues stay clean.

## Workflow
1. **Place the file.** Workflow YAML lives at `.github/workflows/<name>.yml` (`ci.yml` is the default choice). It takes effect on the next push.
2. **Pick triggers.** Default set: `push` and `pull_request` on the main branch. Add `schedule` for recurring jobs (cron is UTC, fields are `minute hour day-of-month month day-of-week`, e.g. `0 9 * * 1` = Mondays 09:00 UTC) and `workflow_dispatch` for a manual "Run workflow" button.
3. **Define one job, then grow.** Every job needs `runs-on: ubuntu-latest`, then ordered `steps`. A step is either `uses:` (a pinned action, e.g. `actions/checkout@v7`) or `run:` (shell commands).
4. **Write the pipeline steps.** Checkout → set up the runtime → restore the dependency cache → install → test. Keep install and test as plain `run:` commands so the same file works for any language.
5. **Add a matrix** when testing multiple versions: `strategy.matrix` lists the axes and steps reference `${{ matrix.python-version }}`. Default to `fail-fast: false` so one failing version doesn't cancel the rest.
6. **Cache dependencies** with `actions/cache@v6`: `path` = the cache dir (`~/.cache/pip`, `~/.npm`), `key` = `${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}`, and `restore-keys` falling back to the OS prefix so a changed lockfile still gets a partial hit.
7. **Wire secrets.** Repo secrets live under Settings → Secrets and variables → Actions. Reference them as `${{ secrets.NAME }}` and pass them through `env:` on the step; never `echo` them into logs. `GITHUB_TOKEN` is auto-provided and fine for API reads — pass a named secret for anything that writes.
8. **Kill stale runs.** Put top-level `concurrency: { group: ${{ github.workflow }}-${{ github.ref }}, cancel-in-progress: true }` so a new push cancels the previous in-progress run on the same branch.

Example — minimal Python CI with matrix, caching, and concurrency:

```yaml
name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 9 * * 1'
  workflow_dispatch:

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        python-version: ['3.12', '3.13']
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-python@v7
        with:
          python-version: ${{ matrix.python-version }}
      - uses: actions/cache@v6
        with:
          path: ~/.cache/pip
          key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
          restore-keys: |
            ${{ runner.os }}-pip-
      - run: python -m pip install --upgrade pip && pip install -r requirements.txt
      - run: pytest
```

Node swap: `actions/setup-node@v6` with `node-version: ${{ matrix.node-version }}`, cache path `~/.npm`, key `${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}`.

## Output Contract
Hand back a complete `.github/workflows/<name>.yml` that parses as valid YAML, plus a 2-4 line note: which triggers it has, which runtime versions it tests, and which secrets it expects. If asked to also deploy or publish to a registry, flag that as an extension, not part of the minimal pipeline.

## Operating Rules
1. Pin every action to a current major version — verified 2026-09-17: `actions/checkout@v7`, `actions/setup-python@v7`, `actions/setup-node@v6`, `actions/cache@v6`. Re-verify before trusting an older pin.
2. Default to the `pull_request` trigger; it runs fork PRs without access to secrets. Treat `pull_request_target` as security-sensitive and default away from it.
3. Default to passing secrets through `env:` (`API_KEY: ${{ secrets.API_KEY }}`); never print a secret value in logs.
4. Put `workflow_dispatch` on every scheduled job so a failed nightly can be re-run by hand.
5. Prefer the setup action's built-in cache (`cache: 'pip'` on setup-python, `cache: 'npm'` on setup-node, plus `cache-dependency-path` when the lockfile isn't at root); reach for `actions/cache` directly only for custom cache paths.
6. Scope `schedule` crons to the lowest frequency the job needs — default to weekly for slow suites, not hourly.
7. Prefer `ubuntu-latest` and `fail-fast: false`, but override when the project genuinely needs macOS/Windows runners or a matrix axis where any single failure is a hard block.
