Authors fast, cheap, maintainable GitHub Actions workflows applying 2026 best practices: caching with `hashFiles` + `restore-keys`, parallelization via matrix + artifacts, reusability (composite actions for steps, reusable workflows for jobs), security (SHA-pinned actions, least-privilege `GITHUB_TOKEN`, concurrency), trackable errors (named steps, step summaries, annotations, and stdout/stderr that always reaches the run log so agents can act on failures), and feedback for comment-triggered runs (👀 acknowledgement reaction on start, 🚀/👎 outcome reaction plus a run-linked comment at the end). Two modes: `scaffold` (default) generates workflow YAML; `review` audits an existing workflow against the same rules. Use when creating CI/CD pipelines, optimizing slow workflows, deduping copy-pasted YAML across repos, or auditing workflow security. Triggers on "github action", "github workflow", "ci pipeline", "create workflow", "speed up ci", "review my workflow", "/github-actions-author".
67
84%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Generate or audit GitHub Actions workflow YAML against 2026 best practices for speed, cost, reusability, and security.
This
SKILL.mdis a thin index. Detailed rules live inrules/*.mdand load on demand. Drop-in starters live intemplates/*.md. The decision tree for picking a shape lives inreferences/decision-tree.md.
This applies to both modes and outranks every other preference in this skill.
Every command in every scaffolded or reviewed workflow must write its stdout and stderr to the job log.
Output that lands only in a file, only in an artifact, only in $GITHUB_STEP_SUMMARY, or in /dev/null is invisible to gh run view <run-id> --log-failed — the only surface /ci-auto-fix, /implement-suggestion, and an on-call human read a failure from.
A failing step that printed nothing cannot be diagnosed or fixed by an agent; it can only be escalated.
Minimum bar for every run: block:
- name: Run unit tests
shell: bash
run: |
set -euo pipefail
npm test 2>&1 | tee test-output.log # tee, never `> file`Forbidden outright: > /dev/null, 2>/dev/null, cmd > out.txt 2>&1, --silent, --quiet, -q, a machine-only reporter with no human output, and || true without echoing the captured output and exit code.
tee requires set -o pipefail, otherwise the step goes green on a failed command.
Full rule, decision table, examples, and the review-mode grep:
rules/log-output-visibility.md — read it in Phase 4 of scaffold and in every review.
Parse $ARGUMENTS (first token):
| Mode | Default | Trigger |
|---|---|---|
scaffold | yes | Default. "create", "scaffold", "new workflow", or no token. |
review | "review", "audit", path to an existing .github/workflows/*. |
State the detected mode and target in one line before continuing:
Mode: scaffold
Target: .github/workflows/ci.ymlFive phases. Each has a gate; do not proceed until it passes.
| Phase | Name | Rule file | Gate |
|---|---|---|---|
| 0 | Intent + shape | references/decision-tree.md | Trigger, stack, and shape (single / matrix / reusable) confirmed. |
| 1 | Anatomy + triggers | rules/workflow-anatomy.md, rules/triggers-and-concurrency.md | on: block scoped (branches + paths), concurrency set. |
| 2 | Speed (cache + parallel) | rules/caching.md, rules/parallelization.md | Cache key is hashFiles-based with restore-keys; independent jobs run in parallel. |
| 3 | Reusability | rules/reusability.md | Any block used > 1 place is extracted to a composite action or reusable workflow. |
| 4 | Security + errors | rules/security.md, rules/observability.md, rules/log-output-visibility.md, rules/feedback.md | Third-party actions SHA-pinned, permissions: minimal, every step named, failures surface a stack-trace path, every command's stdout + stderr reaches the run log, and any comment-triggered workflow — including a workflow_dispatch a comment or bot fired, but not one fired from the Actions UI — acknowledges (👀) and reports its outcome (🚀/👎 + run link). |
Ask in one batched message:
issue_comment,
pull_request_review_comment, pull_request_review)? Which branches?
Which path globs (to skip irrelevant runs)? Comment-triggered runs
additionally require acknowledgement + outcome feedback — see
rules/feedback.md.rules/reusability.md.Repeat the answers back before generating.
Walk each phase using the linked rule file. Each rule is self-contained and includes a decision table plus a good/bad example.
Run the Definition of Done checklist below.
Read the target .yml and produce a structured report — do not mutate
unless asked.
Parse the workflow: triggers, jobs, steps, permissions, concurrency.
Measure the run metrics — report each metric when computable; print
n/a (<reason>) otherwise (no runs yet, logs expired, no cache steps).
Average run duration over the last 10 completed runs:
gh run list --workflow <file>.yml --status completed --limit 10 \
--json startedAt,updatedAt \
--jq 'map((.updatedAt | fromdate) - (.startedAt | fromdate))
| add / length | round
| "\(. / 60 | floor)m\(. % 60)s"'Cache hit rate over the last 10 completed runs — count cache-restore
outcomes in the logs (hit rate = Cache restored ÷ total restore
attempts; logs older than the retention window return nothing, so
report n/a rather than guessing):
gh run list --workflow <file>.yml --status completed --limit 10 \
--json databaseId --jq '.[].databaseId' \
| while read -r id; do
gh run view "$id" --log 2>/dev/null \
| grep -hoE 'Cache restored from key|Cache not found'
done | sort | uniq -cFor each rule file in rules/, mark PASS / WARN /
FAIL with one line of evidence (line N: <quote>).
Log visibility is mandatory in every review — run the grep in
rules/log-output-visibility.md
and report every unjustified hit as a FAIL.
End with a prioritised "Top 3 fixes" list — biggest speed / cost / security wins first.
Offer to apply the fixes if the user wants — switch to scaffold
mode for that section.
Format:
Workflow: .github/workflows/ci.yml
Lines: 142
Jobs: 4
Average run (last 10): 7m12s # or: n/a (no completed runs)
Cache hit rate (last 10): 30% # or: n/a (logs expired / no cache steps)
Anatomy: PASS
Triggers + concurrency: WARN — no `cancel-in-progress` on PR (line 8)
Caching: FAIL — primary key uses `github.sha`, no `restore-keys` (line 34)
Parallelization: PASS
Reusability: WARN — install-deps duplicated across 3 jobs (lines 28, 71, 94)
Security: FAIL — `actions/checkout@v4` tag-pinned, no SHA (line 22)
Observability: WARN — 4 unnamed steps (lines 31, 45, 68, 102)
Log visibility: FAIL — `npm test > test.log 2>&1` hides all output (line 57); `npm ci --silent` (line 29)
Top 3 fixes:
1. Replace `github.sha` cache key with `${{ hashFiles('package-lock.json') }}` + restore-keys (line 34) — expected 60-80% faster on cache hits.
2. SHA-pin every third-party action, comment with the version (line 22, 38, 51).
3. Extract install-deps into `.github/actions/setup-node-deps/action.yml` (composite) — removes 2x 40 LOC duplication.Load on demand — do not preload.
Drop-in starters in templates/:
node-ci.yml.md — Node.js CI with cache, matrix, parallel jobs.python-ci.yml.md — Python CI with pip cache.reusable-workflow.yml.md — workflow_call callee + caller.composite-action.yml.md — .github/actions/<name>/action.yml.deploy-oidc.yml.md — deploy with OIDC, no long-lived secrets.node_modules.
Use actions/setup-node@<sha> { cache: 'npm' } or actions/cache@<sha> keyed by hashFiles('lockfile') with restore-keys fallback.ci.yml, deploy.yml,
release.yml, scheduled.yml. Resist the mega-workflow.actions/checkout@<40-hex> # v4.2.0.GITHUB_TOKEN. Start with permissions: {} at
the workflow level; grant per-job. Read-only by default in
2023+ repos — keep it that way.concurrency is mandatory. PRs use cancel-in-progress: true;
deploys use cancel-in-progress: false. No exceptions.run: blocks are unsearchable in logs
and unsourceable in failure annotations.tee, never >; no --silent / --quiet / /dev/null.
The log is the only thing gh run view --log-failed returns, and it is
what agents act on. See
rules/log-output-visibility.md.issue_comment, pull_request_review_comment,
pull_request_review, or a workflow_dispatch a comment or bot fired)
is invisible. Acknowledge as the first step, then report the outcome
on both paths — a 🚀/👍 reaction on success, a 👎 reaction plus a
comment linking the run on failure. pull_request_review has no
reactable comment, so it uses a single sticky PR comment for both beats.
See rules/feedback.md.@main / @latest / unpinned third-party action.${{ github.sha }}.permissions: write-all (or the default, unset, on a pre-2023 repo).jobs: (it can't — that's a workflow).cancel-in-progress: true on a deploy workflow.on: push: triggering on every branch and every path.run: blocks with no name:.secrets: map./dev/null instead of tee-d to the log.--silent / --quiet / -q on a step whose job is to report.$GITHUB_STEP_SUMMARY.|| true or continue-on-error: true with nothing echoed.tee without set -o pipefail (green job, failed command).A scaffold run is done when:
on: block is scoped to the relevant branches and paths.concurrency is set with the correct cancel-in-progress value
for the workflow type.permissions: is set at the workflow level (or every job) and
lists only what each job actually needs.# vX.Y.Z comment.hashFiles(<lockfile>) and includes runner.os
(plus matrix axes); restore-keys is present.needs:.name: that reads as a sentence ("Install
dependencies", not npm-ci).$GITHUB_STEP_SUMMARY./dev/null, no file-only redirection, no --silent / --quiet,
no machine-only reporter.run: block that pipes to tee (or any pipe) sets
set -o pipefail.|| true / continue-on-error: true step echoes the captured
output and its exit code.rules/log-output-visibility.md
returns no unjustified hits.issue_comment,
pull_request_review_comment, pull_request_review, or a
workflow_dispatch a comment or bot fired, per the trigger table in
rules/feedback.md — the
workflow acknowledges as its first step and reports the outcome on both
paths, with issues: write / pull-requests: write granted and the
command gated before it acknowledges. Where the trigger carries a
reactable comment that means a 👀 reaction first, then a 🚀/👍 reaction
on success and a 👎 reaction plus a run-linked comment on failure.
A pull_request_review trigger carries none — GitHub exposes no
reactions endpoint for a review — so it uses a single sticky PR comment
for both beats. A workflow_dispatch fired from the Actions UI has no
triggering comment and is out of scope.id-token: write is set at the job level only.A review run is done when:
39b3f44
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.