| name | audit-cicd |
| description | Audit CI/CD pipelines (GitHub Actions) for cost, speed, and safety. Use when the GitHub Actions bill is high, when the user mentions Actions minutes, runner cost, workflow cost, slow CI, artifact/cache storage, or wants a CI/CD / workflow audit. Finds double-billing triggers, missing concurrency, macOS/large runners on push, missing path filters, long artifact retention, and doomed jobs — then proposes fixes that never delete tests or break deploys.
|
| license | MIT |
CI/CD Audit Skill
Systematic audit of GitHub Actions workflows to cut the Actions bill (minutes +
storage) and speed up CI without losing test coverage or deploy safety.
Uses the gh CLI for live billing, run history, and storage data.
Step 0: Inventory the account and pipelines
Measure before optimizing. Only private repos consume the paid minute
allowance; public repos get free minutes — don't spend effort there.
gh repo list <owner> --limit 200 --json name,visibility,isArchived,pushedAt \
--jq 'sort_by(.pushedAt)|reverse|.[]|select(.visibility=="PRIVATE" and .isArchived==false)|"\(.name)\t\(.pushedAt[0:10])"'
gh api "repos/<owner>/<repo>/actions/workflows" --jq '.workflows[]|select(.state=="active")|.name'
gh run list --repo <owner>/<repo> --created ">=YYYY-MM-DD" --limit 200 --json databaseId --jq 'length'
gh api "repos/<owner>/<repo>/actions/artifacts" --paginate --jq '[.artifacts[]|select(.expired==false)|.size_in_bytes]|add'
gh api "repos/<owner>/<repo>/actions/cache/usage" --jq '.active_caches_size_in_bytes'
Rank repos by runs × runner-multiplier. A macos-* job counts ~10x a
ubuntu job; *-large/bigger runners cost more than standard.
Step 1: Anti-pattern scan (per workflow)
For each .github/workflows/*.yml, check for the recurring cost drivers:
Step 2: Cost levers (highest impact first)
| Lever | Fix | Impact |
|---|
| macOS/large on push | Gate to workflow_dispatch / tags | Very high (~10x) |
| Doomed heavy jobs | needs: [cheap-gate] so they skip on failure | High |
| Double-billing triggers | Drop the redundant trigger (keep the gate) | High |
| No concurrency | Add cancel-in-progress | Medium |
| No path filters | paths: / paths-ignore: | Medium |
| Daily crons | Move to weekly | Medium |
| Artifact retention | retention-days: 1–7 + if: failure() | Storage |
| Repeated installs | Cache deps / Playwright browsers / build cache | Medium |
Step 3: Fix patterns
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
build-ios:
runs-on: macos-15
if: ${{ inputs.platform == 'ios' || startsWith(github.ref, 'refs/tags/') }}
timeout-minutes: 60
- uses: actions/upload-artifact@v4
if: failure()
with: { name: report, path: report/, retention-days: 3 }
env: { GITLEAKS_ENABLE_UPLOAD_ARTIFACT: "false" }
Step 4: Storage cleanup (destructive — confirm first)
Artifacts and caches are ephemeral CI outputs; deleting them is safe but
irreversible. Confirm with the user, then:
gh api "repos/<owner>/<repo>/actions/artifacts" --paginate \
--jq '.artifacts[]|select(.expired==false)|.id' \
| xargs -I{} gh api -X DELETE "repos/<owner>/<repo>/actions/artifacts/{}"
gh api "repos/<owner>/<repo>/actions/caches" --paginate --jq '.actions_caches[].id' \
| xargs -I{} gh api -X DELETE "repos/<owner>/<repo>/actions/caches/{}"
Safety rules (do NOT trade coverage or deploys for cost)
- Never delete, skip, or weaken a test to save minutes. Make advisory
scans (
npm audit, CVE scans) continue-on-error or move them to a
schedule — keep the check.
- Keep the deploy gate. If
deploy.yml triggers on the CI workflow_run
for push, do not remove the push trigger from CI.
- Only share a build across jobs when their build env is identical. Reusing
an artifact built with different secrets/flags (prod env, QA build stamps)
ships or tests the wrong bundle.
- Update branch protection when renaming/merging jobs. If a required status
check's job name changes,
PATCH required_status_checks.contexts in the
same change or merges hang. Verify:
gh api repos/<owner>/<repo>/branches/<main>/protection/required_status_checks --jq '.contexts'
- Verify with live runs. After pushing, confirm no
startup_failure
(YAML parses) and that intended jobs skip/run:
gh run view <id> --json jobs --jq '.jobs[]|"\(.conclusion // .status) \(.name)"'
Account backstops (one-time, GitHub UI — cannot be set via API)
- Default artifact/log retention → Settings → Actions → General → drop from
90 days to ~14 (applies to all repos, including future ones).
- Spending budget → Settings → Billing → Budgets → set an Actions budget
with 75/90/100% alerts; keep "Stop usage" off so production deploys never
hard-break.
- Rotate any secret leaked in a workflow/remote (the user must revoke;
you can only strip it from configs).
Output: CI/CD Cost Audit Report
## CI/CD Audit: [owner]
### Spend snapshot
- Actions billable: ~$X/mo (private repos exhaust the included minutes)
- Top spenders: [repo — ~$Y, driver], ...
- Storage: [X GB artifacts / Y GB caches]
### Findings (prioritized)
| # | Repo | Workflow | Anti-pattern | Fix | Impact |
|---|------|----------|--------------|-----|--------|
| 1 | repo | build-mobile.yml | macOS on every push | dispatch/tag-only | ~10x |
### Already healthy
- [repos/workflows already using concurrency, gated runners, path filters]
### Manual actions (user-only)
- [ ] Default artifact/log retention → 14 days
- [ ] Actions spending budget + alerts
- [ ] Rotate leaked secret(s), if any
### Expected outcome
~$A → ~$B/mo, no loss of test coverage or deploy safety.