Use when designing matrix builds (OS x version x dimension), composing reusable workflows, gating jobs on conditions, doing OIDC to AWS/GCP/Cloudflare without long-lived secrets, sharding tests across runners, or fixing cache invalidation. Triggers: workflow_call between repos, fail-fast: false tradeoffs, exclude/include matrix surgery, environment protection rules, OIDC trust policy setup, concurrency cancellation on duplicate pushes, artifact reuse across jobs, conditional job matrices comp...
Pro scans all 5 files and shows the line behind each finding
Scanned 9/24/2026
npx -y skills add curiositech/port-daddy --skill github-actions-matrix-patterns --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Github Actions Matrix Patterns?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/curiositech-github-actions-matrix-patterns-port-daddy)More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.
---
license: Apache-2.0
name: github-actions-matrix-patterns
allowed-tools: Read,Write,Edit,Bash,Glob,Grep,WebSearch,WebFetch
description: 'Use when designing matrix builds (OS x version x dimension), composing reusable workflows, gating jobs on conditions, doing OIDC to AWS/GCP/Cloudflare without long-lived secrets, sharding tests across runners, or fixing cache invalidation. Triggers: workflow_call between repos, fail-fast: false tradeoffs, exclude/include matrix surgery, environment protection rules, OIDC trust policy setup, concurrency cancellation on duplicate pushes, artifact reuse across jobs, conditional job matrices computed at runtime. NOT for GitLab CI / CircleCI / Jenkins (different yaml dialects), self-hosted runner administration, or local act dev.'
metadata:
category: DevOps & Infrastructure
tags:
- github-actions
- ci-cd
- matrix
- oidc
- reusable-workflows
- automation
provenance:
kind: first-party
owners: [port-daddy]
pairs-with:
- skill: git-best-practices
reason: Pinned workflow refs, tag discipline, and branch protection that release CI depends on live in the git workflow itself
- skill: agent-pr-authoring
reason: The PR checks these matrix workflows gate are the same checks an agent-authored PR must be written to pass
io-contract:
kind: deliverable
consumes:
- kind: ci-requirement
format: markdown
description: A description of what the pipeline must build/test/deploy -- target OSes, runtimes, cloud provider, and gating rules -- from a human or another agent.
- kind: workflow-plan
format: json
description: A structured plan of the workflow's matrix shape, pinning policy, permissions, auth mode, and cache/concurrency settings, matching schemas/github-actions-matrix-patterns-plan.schema.json.
produces:
- kind: workflow-design
format: markdown
description: The workflow YAML plus the rationale for matrix shape, OIDC trust policy, and cache keys, following this skill's patterns.
- kind: workflow-audit-report
format: json
description: A deterministic pass/fail audit of the workflow-plan against this skill's Quality Gates, as produced by scripts/github_actions_matrix_patterns_audit.mjs.
---
# GitHub Actions Matrix Patterns
Hello-world Actions are easy. Real CI gets stuck on three things: matrix expansion that runs the wrong cells, OIDC trust policies that reject your cloud auth, and cache keys that never invalidate (or invalidate on every commit). This skill catalogs those.
## When to use
- Building across OS x runtime version x dimension (e.g., ubuntu/macos/windows × Node 20/22).
- Sharing a workflow between repos via `workflow_call`.
- Authenticating to AWS/GCP/Cloudflare without storing long-lived keys.
- Sharding a slow test suite across N runners.
- Cancelling outdated CI runs when a new commit lands on the same branch.
## Core capabilities
### Basic matrix
```yaml
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [20, 22]
include:
- os: ubuntu-latest
node: 22
coverage: true # extra dimension only on this cell
exclude:
- os: windows-latest
node: 20 # known broken combination
```
`fail-fast: true` cancels the rest of the matrix on the first failure — fine for dev speed, bad for "tell me everything that's broken." Default to `false` for release CI.
### Reusable workflows (`workflow_call`)
```yaml
# .github/workflows/test.yml
on:
workflow_call:
inputs:
node-version: { type: string, required: false, default: '22' }
coverage: { type: boolean, required: false, default: false }
secrets:
CODECOV_TOKEN: { required: false }
outputs:
coverage-pct:
value: ${{ jobs.test.outputs.coverage-pct }}
jobs:
test:
runs-on: ubuntu-latest
outputs:
coverage-pct: ${{ steps.report.outputs.pct }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: ${{ inputs.node-version }} }
- run: pnpm test
- if: inputs.coverage
id: report
run: echo "pct=$(node scripts/coverage-pct.js)" >> "$GITHUB_OUTPUT"
```
Caller:
```yaml
jobs:
call-test:
uses: ./.github/workflows/test.yml
with: { node-version: '22', coverage: true }
secrets: inherit
```
For cross-repo: `uses: org/repo/.github/workflows/test.yml@v1` and pin to a tag, never `@main`.
### OIDC to a cloud provider
GitHub mints a short-lived OIDC token. The cloud trusts it via a trust policy. No long-lived AWS keys in your secrets.
```yaml
permissions:
id-token: write # mint OIDC
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/github-deploy
aws-region: us-west-2
- run: aws s3 sync ./out s3://my-bucket/
```
AWS trust policy on `github-deploy`:
```json
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com" },
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" },
"StringLike": { "token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:ref:refs/heads/main" }
}
}]
}
```
The `sub` condition is the security boundary. Tighten it to `repo:org/repo:environment:production` for additional protection from a malicious PR.
### Concurrency
Cancel outdated runs when a new commit lands on a branch:
```yaml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
```
For deploy jobs you don't want to cancel mid-flight, scope to PRs only:
```yaml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
```
### Conditional matrices
Compute a matrix at runtime when the dimensions depend on the changed files:
```yaml
jobs:
changed:
runs-on: ubuntu-latest
outputs:
packages: ${{ steps.set.outputs.packages }}
steps:
- uses: actions/checkout@v4
with: { fetch-depth: 0 }
- id: set
run: |
PKGS=$(git diff --name-only origin/main...HEAD | grep '^packages/' | cut -d/ -f2 | sort -u | jq -R . | jq -sc .)
echo "packages=$PKGS" >> "$GITHUB_OUTPUT"
test:
needs: changed
if: ${{ needs.changed.outputs.packages != '[]' }}
runs-on: ubuntu-latest
strategy:
matrix:
package: ${{ fromJson(needs.changed.outputs.packages) }}
steps:
- run: pnpm --filter @myorg/${{ matrix.package }} test
```
### Caching with stable keys
```yaml
- uses: actions/cache@v4
with:
path: ~/.pnpm-store
key: pnpm-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
pnpm-${{ runner.os }}-
```
`key` invalidates when the lockfile changes. `restore-keys` is the partial-hit fallback — restores the latest matching prefix.
For language toolchains, prefer the setup-* actions' built-in caching (`actions/setup-node@v4 with: cache: 'pnpm'`); they handle key construction.
### Sharding tests across runners
```yaml
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4]
steps:
- run: pnpm test --shard=${{ matrix.shard }}/4
```
Most test runners (vitest, jest, playwright) accept `--shard=K/N`. Distribute by file count and rotate the assignment seed weekly to prevent a slow file always landing on shard 1.
### Environments + protection rules
```yaml
jobs:
deploy-prod:
environment: production
runs-on: ubuntu-latest
```
`environment: production` triggers required reviewers, deployment branches policy, wait timer, and environment-scoped secrets. Use this for the production deploy step rather than baking approvals into the workflow logic.
## Anti-patterns
### Pinning to `@main` for third-party actions
**Symptom:** Workflow that worked yesterday fails today; no commits to your repo.
**Diagnosis:** Floating tag — upstream pushed a breaking change.
**Fix:** Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. Or at minimum a major tag your security team has reviewed.
### `secrets.GITHUB_TOKEN` for cross-repo writes
**Symptom:** Workflow fails to push to another repo even with `permissions: contents: write`.
**Diagnosis:** `GITHUB_TOKEN` only has scope to the current repo.
**Fix:** Use a deploy key, fine-grained PAT, or GitHub App token (preferred for orgs).
### Matrix `exclude` without `include`
**Symptom:** Matrix balloons to 30 cells; you exclude 25 of them.
**Diagnosis:** Inverting the polarity. Exclude is for trimming a small handful; include is for adding cells.
**Fix:** Build the small list explicitly with `include` only.
### Cache key that never invalidates
**Symptom:** Stale dependencies on every run; "works locally" recurring.
**Diagnosis:** Key uses only `runner.os`, no hash of the lockfile.
**Fix:** Always `hashFiles('lockfile.path')` in the key. `restore-keys` is fallback, not primary.
### OIDC `sub` claim too permissive
**Symptom:** A fork PR triggers a deploy.
**Diagnosis:** Trust policy `sub: repo:org/repo:*` matches PR refs from forks.
**Fix:** Pin to specific refs: `repo:org/repo:ref:refs/heads/main`, or use environment claims: `repo:org/repo:environment:production`.
### Implicit `permissions: write-all`
**Symptom:** A compromised action exfiltrates a token with full repo write.
**Diagnosis:** Workflow inherits the default permissions, which historically were write-all.
**Fix:** Set top-level `permissions: read-all` and grant per-job: `permissions: { contents: read, id-token: write }`.
## Quality gates
- [ ] Every third-party action pinned to a SHA or audited major version.
- [ ] Top-level `permissions: read-all`; per-job grants minimal.
- [ ] OIDC used instead of long-lived cloud secrets where the provider supports it.
- [ ] Production deploys behind `environment:` with required reviewers.
- [ ] `concurrency` set on PR workflows to cancel outdated runs.
- [ ] Cache keys include a hash of the lockfile they cover.
- [ ] `fail-fast: false` on release CI matrices.
- [ ] Test sharding seed rotated weekly so slow files don't pin a shard.
- [ ] No `secrets: inherit` to forks.
## Deterministic Audit
Before shipping (or reviewing) a workflow, write its shape as a JSON plan matching
`schemas/github-actions-matrix-patterns-plan.schema.json` and run it through the
deterministic auditor:
```bash
node scripts/github_actions_matrix_patterns_audit.mjs --input examples/sample-input.json
```
`auditGithubActionsMatrixPatterns(plan)` (in `scripts/github_actions_matrix_patterns_audit.mjs`)
turns this skill's Quality Gates into machine-checkable rules over structured fields — no
keyword matching: floating action refs, `write-all` (or inherited) permissions, long-lived
cloud secrets where OIDC works, a repo-wildcard OIDC `sub` claim (the fork-PR deploy hole),
`fail-fast` on release CI, inverted exclude polarity, cache keys with no lockfile hash,
missing PR concurrency cancellation, deploys outside an `environment:`, and secrets inherited
by fork PRs. It returns `{ pass, score, findings, recommendations }` and exits 1 on failure,
so a CI gate can reject a workflow plan without re-deriving the reasoning.
`examples/sample-input.json` is a correctly hardened OIDC deploy plan (`pass: true`, zero
findings). See `CHANGELOG.md` for the bundle's history.
## NOT for
- **GitLab CI / CircleCI / Jenkins** — different DSLs, different runners.
- **Self-hosted runner administration** — different domain (autoscaling, security hardening).
- **`act` local development** — local-only quirks not worth conflating.
- **GitHub Apps** beyond their use as token issuers — separate skill (no dedicated skill yet).
- **pnpm workspace filtering, catalogs, hoisting** — once the matrix is fine and the workspace is broken, → `pnpm-workspace-monorepo`.
- **Docker image build cache strategy from CI** — → `dockerfile-build-cache-mastery` for the Dockerfile/buildx side.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!