| name | build-github-workflow |
| description | Scaffolds a GitHub Actions workflow — a YAML file under `.github/workflows/` — with top-level least-priv `permissions:`, SHA-pinned `uses:`, per-job `timeout-minutes`, workflow-level `defaults.run.shell: bash`, scoped triggers, `harden-runner` as first step, `set -euo pipefail` in every multi-line `run:`, and deliberate concurrency posture (cancel-in-progress for PR/push, no-cancel for deploy). Use when the user wants to "scaffold a ci workflow", "create a github workflow", or "build a github actions workflow for X". Not for composite actions (`action.yml` — separate primitive), org rulesets, Dependabot configs, or GitHub Apps.
|
| allowed-tools | Read, Write, Edit, Bash, Grep, Glob |
| argument-hint | [purpose] |
| user-invocable | true |
| references | ["../../_shared/references/github-workflow-best-practices.md","../../_shared/references/primitive-routing.md"] |
| license | MIT |
Build GitHub Workflow
Scaffold a GitHub Actions workflow: a single YAML file under
.github/workflows/ that runs on repository events, holds an explicit
permissions / timeout / concurrency / pinning posture, and stays
under the Checks-UI-and-required-checks contract. The authoring
rubric — anatomy template, authoring principles, patterns that work,
anti-patterns — lives in
github-workflow-best-practices.md.
This skill is the workflow; the principles doc is the rubric.
The scope is a single workflow file. Composite actions,
organization rulesets, Dependabot configs, and GitHub Apps are
separate primitives.
Workflow sequence: 1. Route → 2. Scope Gate → 3. Elicit →
4. Draft → 5. Safety Check → 6. Review Gate → 7. Save → 8. Test
When to use
Also fires when the user phrases the request as:
- "new deploy workflow"
- "write a release workflow"
1. Route
Confirm a GitHub Actions workflow is the right primitive before
asking scaffold questions.
Wrong primitive:
- Composite action (a reusable unit under
.github/actions/<name>/
with an action.yml exposing inputs/outputs/runs) — a
different rubric (input schemas, runs.using, release tagging).
No /build:build-github-action skill exists yet; hand-author.
- Organization ruleset / branch protection config — lives in
GitHub settings, not in
.github/workflows/.
- Dependabot config (
.github/dependabot.yml) — different schema.
- CODEOWNERS —
.github/CODEOWNERS, different schema.
- A Claude Code hook — route to
/build:build-hook.
- A shell script that runs in CI — the workflow is the trigger;
the script goes to
/build:build-bash-script and is called from
the workflow.
Right primitive (YAML file fired by a repository event, runs jobs
on a runner, uses the GITHUB_TOKEN) → proceed to Scope Gate.
2. Scope Gate
Refuse to scaffold — and recommend an alternative — when the request
signals one of these:
- Composite action authoring. Out of scope; hand-author or wait
for
/build:build-github-action. Do not approximate a composite
action shape inside .github/workflows/.
pull_request_target + checkout of PR code. Textbook Actions
CVE vector. Refuse and explain: this combination has no safe
form — fork code executes with base-repo write permissions and
secrets. Offer pull_request (no secrets for forks; safe) or
pull_request_target without PR checkout as alternatives.
@main, @master, or floating semver wildcards for any
uses:. Refuse and explain: mutable refs are unreproducible and
unsafe. Ask the user for a pinnable SHA or major tag; offer to
resolve a SHA via gh api if they approve.
- Secrets in public-repo fork
pull_request workflows. Secrets
can't safely reach fork PR code. Refuse and explain; recommend
splitting into a pull_request (no secrets) and a merge- or
label-triggered workflow (with secrets).
- Self-hosted runners on public-repo
pull_request workflows.
PR code would execute on your infrastructure. Refuse and explain.
- Multi-workflow refactor. This skill scaffolds one file. For
splitting an omnibus workflow or introducing reusable workflows
across several files, iterate — scaffold one at a time.
If any signal fires, state the signal, name the alternative, and
stop. Do not proceed to Elicit.
3. Elicit
If $ARGUMENTS is non-empty, parse it as [purpose] and pre-fill
question 1. Otherwise ask, one question at a time:
1. Purpose — one sentence: what does this workflow do?
("Run pytest on pull requests against main", "Deploy the prod
service on a v* tag push", "Nightly lockfile audit at 03:00 UTC".)
Drives the filename (ci.yml, deploy-prod.yml, audit-locks.yml).
2. Triggers — pick the set deliberately:
push — branch list (e.g., [main]), paths: filter if the
workflow only cares about a subset.
pull_request — branch list, types: (default
[opened, synchronize, reopened] — name explicitly if different).
schedule — cron expression (randomize the minute; never 0).
workflow_dispatch — inputs with type: choice where possible;
document each input.
workflow_call — this becomes a reusable workflow; declare
inputs: and secrets:.
Any use of pull_request_target triggers a Scope Gate re-check —
return to Step 2.
3. Jobs and dependencies — which work splits across which jobs?
Which jobs needs: which? Prefer small jobs with explicit needs:
over one large job; parallelism is free, serial jobs block the DAG.
4. Runner — ubuntu-latest (CI) vs pinned ubuntu-24.04
(release / deploy). Ask: "Is this a release or deploy workflow?
Pin the runner if yes." Self-hosted requires justification; public-repo
pull_request workflows cannot use self-hosted.
5. External actions — full list. For each: full 40-char commit
SHA (user provides, or Claude offers to fetch via gh api with user
approval). First-party actions/*/github/* may tag-pin (@v4)
with an inline comment and Dependabot coverage — otherwise SHA.
6. Deploys? — if yes: target environment: name; required
reviewers configured? Promise the user the environment: key will
be scaffolded; actual protection rules are a GitHub UI configuration.
7. Cloud authentication — OIDC (provider + role ARN/federated
identity) or static secrets (discouraged). If OIDC, scaffold
permissions: id-token: write and the federated-credentials step;
if static, warn and scaffold with a comment pointing to OIDC migration.
8. Concurrency behavior — cancel-in-progress (default for PR/push
workflows) or no-cancel (mandatory for deploy/release — scaffold
rejects cancel-in-progress: true on deploys).
9. Artifacts? — names, retention-days. Skip artifact block if
none.
10. Save path — must be under .github/workflows/. Default:
.github/workflows/<derived-from-purpose>.yml. Confirm the filename.
4. Draft
Produce two artifacts.
Artifact 1: The workflow YAML.
One conditionalized template. Sections marked (if deploy),
(if OIDC), (if schedule), (if destructive), (if has-artifacts)
are omitted when intake rules them out.
name: <Purpose>
on:
push:
branches: [main]
paths: ['<path-filter>']
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
defaults:
run:
shell: bash
jobs:
<job-id>:
name: <Job Name>
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
steps:
- name: Harden runner
uses: step-security/harden-runner@<SHA>
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@<SHA>
with:
persist-credentials: false
fetch-depth: 1
- name: Setup
uses: actions/setup-<lang>@<SHA>
with:
<lang>-version: '<version>'
cache: <ecosystem>
- name: <Action>
env:
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
set -euo pipefail
# <body — extract to a script under .github/scripts/ past ~20 lines>
- name: Upload artifact
uses: actions/upload-artifact@<SHA>
with:
name: <artifact-name>
path: <path>
retention-days: 7
Artifact 2: A commit summary — one paragraph naming what the
workflow does, what triggers it, what it produces (checks,
artifacts, deploys), and the first-run expectation. Used for the
commit message or PR description.
Present both artifacts before safety checks.
5. Safety Check
Review the draft against
github-workflow-best-practices.md
before presenting. Fail any check → revise; the Review Gate is for
user approval, not correctness recovery.
Structure:
- Top-level
name: present
- Triggers scoped (
paths / branches / types named deliberately)
- Top-level
permissions: present and is not write-all
- Workflow-level
defaults.run.shell: bash
- Concurrency group defined;
cancel-in-progress: true iff not a
deploy/release workflow
- Every job has
name: and timeout-minutes:
- Job/step IDs are kebab-case
Pinning:
- Every
uses: is a 40-char commit SHA, or a first-party tag with
an inline comment documenting the exemption
- No
@main, @master, @v*.*, or other mutable ref
- Any
docker:// image has an explicit non-latest tag or
@sha256: digest
Safety:
step-security/harden-runner is the first step of every job
(audit or block mode)
actions/checkout has persist-credentials: false unless a push
step is present
- No
${{ github.event.* }} / github.head_ref / inputs.* in any
run: block body — user-controlled context routed through env:
pull_request_target unused, or if used, no PR-ref checkout
- No
${{ secrets.* }} in top-level env:
- No
${{ secrets.* }} in pull_request workflows without source
gating
- If public-repo
pull_request: no self-hosted runners
Correctness:
- Every multi-line bash
run: block starts with set -euo pipefail
- No deprecated commands (
::set-output, ::set-env, ::add-path)
continue-on-error: true present only on steps with an inline
comment justifying non-blocking behavior
needs: explicit between jobs that depend on each other
Deploy-specific (if deploy):
environment: key present with a name
- Runner pinned (
ubuntu-24.04, not ubuntu-latest)
cancel-in-progress is false or omitted
Artifacts (if has-artifacts):
- Every
actions/upload-artifact has explicit name and
retention-days
6. Review Gate
Present the workflow YAML and the commit summary. Wait for explicit
user approval before writing any file to disk. Write only after this
gate passes.
If the user requests changes, revise and re-present. Continue until
the user explicitly approves or cancels. Proceed to Save only on
explicit approval.
7. Save
Write the approved workflow to the path elicited in Step 3.10 — must
be under .github/workflows/. No other save location is valid for
this primitive.
Show the commit summary for the user to wire into the commit message
or PR description.
8. Test
Offer the audit:
"Run /build:check-github-workflow <path> to audit the scaffolded
workflow against actionlint, zizmor, yamllint, shellcheck on
extracted run: content, plus the seven judgment dimensions?"
The audit is the canonical follow-on; running it once after scaffold
catches anything the Safety Check missed and gives a clean baseline.
Anti-Pattern Guards
- Scaffolding
pull_request_target with PR checkout. Unconditional
refuse. There is no safe form of this combination.
- Scaffolding with
@main / @master / @v*.*. Unconditional
refuse. Ask for a SHA.
- Missing top-level
permissions:. Default GITHUB_TOKEN is too
broad. Every scaffolded workflow has contents: read at minimum
at the top level.
- Missing
timeout-minutes: on any job. Default is 360 minutes;
every job gets an explicit timeout.
- User-controlled expressions in
run: bodies. Route through
env: without exception.
cancel-in-progress: true on deploy. Scaffold rejects this
combination; the deploy concurrency group omits the cancel flag.
- Empty
REQUIRED_CMDS-equivalent. Whatever the Intake
collected, the scaffold uses it — don't scaffold placeholder
steps the user didn't ask for (setup-python when the Intake
said Node, etc.).
Key Instructions
- Refuse cleanly on any of the six Scope Gate signals. These are
hard refuses, not "scaffold-and-warn" cases.
- Save location is always under
.github/workflows/. No other path
is valid.
- SHA-pin universally, including first-party. First-party tag-pinning
is an exception requiring an inline comment and Dependabot coverage.
- Offer to fetch release SHAs via
gh api repos/<owner>/<repo>/git/refs/tags/<tag>
when the user provides a tag instead of a SHA — with user approval
before running gh.
step-security/harden-runner is the first step of every job —
no exceptions; this is the post-tj-actions runtime defense.
- Every multi-line bash
run: block starts with set -euo pipefail.
- Write files to disk only after the Review Gate passes.
- Won't scaffold
pull_request_target + PR checkout — unconditional
refuse.
- Won't scaffold mutable refs (
@main, @master, @v*.*) —
unconditional refuse.
Handoff
Chainable to: /build:check-github-workflow (audit the scaffolded
workflow against actionlint, zizmor, yamllint, shellcheck, and the
seven judgment dimensions); /build:build-bash-script if a run:
block grew past ~20 lines and wants to become a checked-in script
under .github/scripts/.