Github Actions Author
ASecurityAuthors 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 ...
- 13 stars
- 0 votes
- 0 copies
- 3 views
- Added September 3, 2026
Security analysis
100/100Pro scans all 16 files and shows the line behind each finding
npx -y skills add mthines/agent-skills --skill github-actions-author --agent claude-codeAre you the author of Github Actions Author?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/mthines-github-actions-author)---
name: github-actions-author
description: >
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".
disable-model-invocation: true
argument-hint: '[scaffold|review] [<workflow-file>]'
license: MIT
metadata:
author: mthines
version: '1.2.0'
workflow_type: scaffolder
tags:
- github-actions
- ci-cd
- workflows
- caching
- reusable-workflows
- composite-actions
- matrix
- security
- oidc
- logging
- feedback
- reactions
---
# GitHub Actions Author
Generate or audit GitHub Actions workflow YAML against 2026 best
practices for speed, cost, reusability, and security.
> **This `SKILL.md` is a thin index.** Detailed rules live in
> [`rules/*.md`](./rules/) and load on demand. Drop-in starters live in
> [`templates/*.md`](./templates/). The decision tree for picking a
> shape lives in [`references/decision-tree.md`](./references/decision-tree.md).
---
## Non-negotiable โ every step's output reaches the run log
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:
```yaml
- 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`](./rules/log-output-visibility.md) โ read it in Phase 4 of scaffold and in every review.
---
## Mode Detection
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.yml
```
---
## Scaffold Workflow
Five phases. Each has a gate; do not proceed until it passes.
| Phase | Name | Rule file | Gate |
| ----- | --------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| 0 | Intent + shape | [`references/decision-tree.md`](./references/decision-tree.md) | Trigger, stack, and shape (single / matrix / reusable) confirmed. |
| 1 | Anatomy + triggers | [`rules/workflow-anatomy.md`](./rules/workflow-anatomy.md), [`rules/triggers-and-concurrency.md`](./rules/triggers-and-concurrency.md) | `on:` block scoped (branches + paths), concurrency set. |
| 2 | Speed (cache + parallel) | [`rules/caching.md`](./rules/caching.md), [`rules/parallelization.md`](./rules/parallelization.md) | Cache key is `hashFiles`-based with `restore-keys`; independent jobs run in parallel. |
| 3 | Reusability | [`rules/reusability.md`](./rules/reusability.md) | Any block used > 1 place is extracted to a composite action or reusable workflow. |
| 4 | Security + errors | [`rules/security.md`](./rules/security.md), [`rules/observability.md`](./rules/observability.md), [`rules/log-output-visibility.md`](./rules/log-output-visibility.md), [`rules/feedback.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). |
### Phase 0 โ Intent and shape
Ask in **one** batched message:
1. **Workflow purpose** โ one sentence. CI, deploy, release, scheduled,
manual, or composite/reusable shared piece?
2. **Trigger surface** โ push, pull_request, schedule, workflow_dispatch,
workflow_call, or a comment / slash command (`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/feedback.md).
3. **Stack** โ Node (npm/yarn/pnpm/bun), Python (pip/uv/poetry), Go,
Rust, Java/Gradle, Docker, mixed?
4. **Shape** โ single job, matrix (axes?), build-then-test (artifact
hand-off), or split jobs (lint / typecheck / test / build) running
in parallel?
5. **Reuse** โ is this YAML duplicated across repos or workflows? If so,
refactor target is a composite action (steps) or reusable workflow
(jobs) โ see [`rules/reusability.md`](./rules/reusability.md).
6. **Secrets** โ none, repo secrets, environment secrets, or OIDC to a
cloud provider (AWS/GCP/Azure)?
Repeat the answers back before generating.
### Phase 1โ4
Walk each phase using the linked rule file. Each rule is self-contained
and includes a decision table plus a good/bad example.
### Phase 5 โ Self-check
Run the [Definition of Done](#definition-of-done) checklist below.
---
## Review Workflow
Read the target `.yml` and produce a structured report โ do not mutate
unless asked.
1. Parse the workflow: triggers, jobs, steps, permissions, concurrency.
2. 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:
```bash
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):
```bash
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 -c
```
3. For each rule file in [`rules/`](./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`](./rules/log-output-visibility.md#verification)
and report every unjustified hit as a **FAIL**.
4. End with a prioritised "Top 3 fixes" list โ biggest speed / cost /
security wins first.
5. 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.
```
---
## Required Reading by Phase
Load on demand โ do not preload.
| Phase | Files |
| ----- | --------------------------------------------------------------------------------------------------------------------------- |
| 0 | [`references/decision-tree.md`](./references/decision-tree.md) |
| 1 | [`rules/workflow-anatomy.md`](./rules/workflow-anatomy.md), [`rules/triggers-and-concurrency.md`](./rules/triggers-and-concurrency.md) |
| 2 | [`rules/caching.md`](./rules/caching.md), [`rules/parallelization.md`](./rules/parallelization.md) |
| 3 | [`rules/reusability.md`](./rules/reusability.md) |
| 4 | [`rules/security.md`](./rules/security.md), [`rules/observability.md`](./rules/observability.md), [`rules/log-output-visibility.md`](./rules/log-output-visibility.md), [`rules/feedback.md`](./rules/feedback.md) |
Drop-in starters in [`templates/`](./templates/):
- [`node-ci.yml.md`](./templates/node-ci.yml.md) โ Node.js CI with cache, matrix, parallel jobs.
- [`python-ci.yml.md`](./templates/python-ci.yml.md) โ Python CI with pip cache.
- [`reusable-workflow.yml.md`](./templates/reusable-workflow.yml.md) โ `workflow_call` callee + caller.
- [`composite-action.yml.md`](./templates/composite-action.yml.md) โ `.github/actions/<name>/action.yml`.
- [`deploy-oidc.yml.md`](./templates/deploy-oidc.yml.md) โ deploy with OIDC, no long-lived secrets.
---
## Core Principles
1. **Cache the package manager's global directory, not `node_modules`.**
Use `actions/setup-node@<sha> { cache: 'npm' }` or `actions/cache@<sha>` keyed by `hashFiles('lockfile')` with `restore-keys` fallback.
2. **One responsibility per workflow file.** `ci.yml`, `deploy.yml`,
`release.yml`, `scheduled.yml`. Resist the mega-workflow.
3. **Parallelize first, then cache.** Splitting lint / typecheck / test
into separate jobs gives near-linear wins; cache reduces the cold
tail.
4. **Composite actions for steps, reusable workflows for jobs.** Never
put job orchestration into a composite action; never use a reusable
workflow to wrap two shell lines.
5. **SHA-pin every third-party action.** Tags are mutable; SHAs are
immutable. `actions/checkout@<40-hex> # v4.2.0`.
6. **Least-privilege `GITHUB_TOKEN`.** Start with `permissions: {}` at
the workflow level; grant per-job. Read-only by default in
2023+ repos โ keep it that way.
7. **`concurrency` is mandatory.** PRs use `cancel-in-progress: true`;
deploys use `cancel-in-progress: false`. No exceptions.
8. **Name every step.** Anonymous `run:` blocks are unsearchable in logs
and unsourceable in failure annotations.
9. **Never swallow output.** Every command's stdout and stderr must reach
the run log โ `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`](./rules/log-output-visibility.md).
10. **Comment-triggered runs must give feedback.** A workflow with no PR
status check (`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`](./rules/feedback.md).
---
## Anti-patterns (one-liners โ full list in each rule file)
- `@main` / `@latest` / unpinned third-party action.
- Primary cache key includes `${{ github.sha }}`.
- `permissions: write-all` (or the default, unset, on a pre-2023 repo).
- Lint, typecheck, and test glued sequentially in one job.
- Composite action that defines `jobs:` (it can't โ that's a workflow).
- Reusable workflow used to wrap two shell steps.
- `cancel-in-progress: true` on a deploy workflow.
- Unscoped `on: push:` triggering on every branch and every path.
- 20 anonymous `run:` blocks with no `name:`.
- Secrets passed as workflow inputs instead of `secrets:` map.
- Output redirected to a file or `/dev/null` instead of `tee`-d to the log.
- `--silent` / `--quiet` / `-q` on a step whose job is to report.
- Machine-only reporter (JUnit/SARIF/JSON) with no human output on stdout.
- Diagnostics uploaded as an artifact or written only to `$GITHUB_STEP_SUMMARY`.
- `|| true` or `continue-on-error: true` with nothing echoed.
- `tee` without `set -o pipefail` (green job, failed command).
- Comment/slash-command workflow that never reacts to the triggering comment (user can't tell it ran).
- Feedback only on success โ a failed comment-triggered run left with no reaction or comment.
- Failure reaction (๐) with no comment linking the run (user knows it broke, not where).
- Reacting to a comment before gating the command by author / prefix (any user drives the bot).
---
## Definition of Done
A **scaffold** run is done when:
- [ ] Workflow purpose, triggers, stack, and shape were confirmed
before any YAML was written.
- [ ] `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.
- [ ] Every third-party action is pinned to a full-length commit SHA
with a `# vX.Y.Z` comment.
- [ ] Cache key uses `hashFiles(<lockfile>)` and includes `runner.os`
(plus matrix axes); `restore-keys` is present.
- [ ] Independent jobs run in parallel; sequential dependencies are
explicit via `needs:`.
- [ ] Repeated step blocks are extracted (composite action) or
repeated job blocks are extracted (reusable workflow).
- [ ] Every step has a `name:` that reads as a sentence ("Install
dependencies", not `npm-ci`).
- [ ] Failure paths surface to the PR via annotations or
`$GITHUB_STEP_SUMMARY`.
- [ ] Every command's stdout **and** stderr reaches the run log โ no
`/dev/null`, no file-only redirection, no `--silent` / `--quiet`,
no machine-only reporter.
- [ ] Every `run:` block that pipes to `tee` (or any pipe) sets
`set -o pipefail`.
- [ ] Every `|| true` / `continue-on-error: true` step echoes the captured
output and its exit code.
- [ ] The log-visibility grep from
[`rules/log-output-visibility.md`](./rules/log-output-visibility.md#verification)
returns no unjustified hits.
- [ ] If comment-triggered and producing no PR status check โ `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`](./rules/feedback.md#when-this-rule-applies) โ 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.
- [ ] If using OIDC, `id-token: write` is set at the job level only.
- [ ] User received a one-paragraph summary of what was created and
where to commit it.
A **review** run is done when:
- [ ] Every rule produced a PASS / WARN / FAIL with line evidence.
- [ ] Log visibility was checked with the grep and reported explicitly.
- [ ] Top 3 fixes are ranked by impact (speed, cost, or security).
- [ ] User received an offer to apply the fixes interactively.
Files in this skill
- SKILL.md
- references/decision-tree.md
- rules/caching.md
- rules/feedback.md
- rules/log-output-visibility.md
- rules/observability.md
- rules/parallelization.md
- rules/reusability.md
- rules/security.md
- rules/triggers-and-concurrency.md
- rules/workflow-anatomy.md
- templates/composite-action.yml.md
- templates/deploy-oidc.yml.md
- templates/node-ci.yml.md
- templates/python-ci.yml.md
- templates/reusable-workflow.yml.md
Attribution
Comments
Loading commentsโฆ
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.