plugins/build/skills/build-github-workflow/SKILL.md
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.
npx skillsauth add bcbeidel/wos build-github-workflowInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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
Also fires when the user phrases the request as:
Confirm a GitHub Actions workflow is the right primitive before asking scaffold questions.
Wrong primitive:
.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..github/workflows/..github/dependabot.yml) — different schema..github/CODEOWNERS, different schema./build:build-hook./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.
Refuse to scaffold — and recommend an alternative — when the request signals one of these:
/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.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).pull_request workflows.
PR code would execute on your infrastructure. Refuse and explain.If any signal fires, state the signal, name the alternative, and stop. Do not proceed to 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.
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> # from Step 3.1
on:
# <triggers from Step 3.2, scoped with paths/branches/types>
push:
branches: [main]
paths: ['<path-filter>']
permissions: # top-level least-priv
contents: read
# id-token: write # (if OIDC)
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true # (false or omitted for deploy)
defaults:
run:
shell: bash
jobs:
<job-id>: # kebab-case IDs
name: <Job Name>
runs-on: ubuntu-latest # pinned for deploy/release
timeout-minutes: 20 # every job
# environment: <env-name> # (if deploy)
# needs: <upstream-job-id> # (if dependent)
permissions: # (elevate only if job needs it)
contents: read
steps:
- name: Harden runner
uses: step-security/harden-runner@<SHA> # v<N>.<M>.<P>
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@<SHA> # v<N>
with:
persist-credentials: false
fetch-depth: 1
# <language setup with built-in caching>
- name: Setup
uses: actions/setup-<lang>@<SHA> # v<N>
with:
<lang>-version: '<version>'
cache: <ecosystem>
# <OIDC cloud auth — if Step 3.7 = OIDC>
# - name: Configure AWS credentials
# uses: aws-actions/configure-aws-credentials@<SHA>
# with:
# role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
# aws-region: us-east-1
- name: <Action>
env:
# Route user-controlled context through env, NEVER ${{ }} in run
PR_TITLE: ${{ github.event.pull_request.title }}
run: |
set -euo pipefail
# <body — extract to a script under .github/scripts/ past ~20 lines>
# (if has-artifacts)
- name: Upload artifact
uses: actions/upload-artifact@<SHA> # v<N>
with:
name: <artifact-name>
path: <path>
retention-days: 7 # minimum the workflow needs
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.
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:
name: presentpaths / branches / types named deliberately)permissions: present and is not write-alldefaults.run.shell: bashcancel-in-progress: true iff not a
deploy/release workflowname: and timeout-minutes:Pinning:
uses: is a 40-char commit SHA, or a first-party tag with
an inline comment documenting the exemption@main, @master, @v*.*, or other mutable refdocker:// image has an explicit non-latest tag or
@sha256: digestSafety:
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${{ 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${{ secrets.* }} in top-level env:${{ secrets.* }} in pull_request workflows without source
gatingpull_request: no self-hosted runnersCorrectness:
run: block starts with set -euo pipefail::set-output, ::set-env, ::add-path)continue-on-error: true present only on steps with an inline
comment justifying non-blocking behaviorneeds: explicit between jobs that depend on each otherDeploy-specific (if deploy):
environment: key present with a nameubuntu-24.04, not ubuntu-latest)cancel-in-progress is false or omittedArtifacts (if has-artifacts):
actions/upload-artifact has explicit name and
retention-daysPresent 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.
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.
Offer the audit:
"Run
/build:check-github-workflow <path>to audit the scaffolded workflow against actionlint, zizmor, yamllint, shellcheck on extractedrun: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.
pull_request_target with PR checkout. Unconditional
refuse. There is no safe form of this combination.@main / @master / @v*.*. Unconditional
refuse. Ask for a SHA.permissions:. Default GITHUB_TOKEN is too
broad. Every scaffolded workflow has contents: read at minimum
at the top level.timeout-minutes: on any job. Default is 360 minutes;
every job gets an explicit timeout.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.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.)..github/workflows/. No other path
is valid.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.run: block starts with set -euo pipefail.pull_request_target + PR checkout — unconditional
refuse.@main, @master, @v*.*) —
unconditional refuse.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/.
tools
Use when the user wants to "audit a help skill", "review my plugin index", or "verify my help-skill is up to date". Audits a plugins/<plugin>/skills/help/SKILL.md against the help-skill rubric — coverage, freshness, frontmatter fidelity, plus five judgment dimensions and a trigger-collision check.
tools
Use when the user wants to "scaffold a help skill", "add a /<plugin>:help command", or "build a plugin index skill", or wants to give a plugin an orientation surface that lists its skills and common workflows. Produces a SKILL.md at plugins/<plugin>/skills/help/SKILL.md.
tools
Audits pair-level integrity of a primitive-pair (the artifact `/build:build-skill-pair` produces) by walking the four required artifact slots — principles doc, `build-<primitive>/SKILL.md`, `check-<primitive>/SKILL.md`, and the `primitive-routing.md` registration — and reports cross-artifact issues a per-SKILL.md checker cannot see: missing principles doc, divergent principles paths between halves, absent routing registration, missing build→check handoff. Per-half structural compliance with the unified pattern (`check-skill-pattern.md`) is delegated to `plugins/build/_shared/scripts/check_skill_pattern.py`. Use when the user wants to "audit a skill pair", "review a primitive pair", or "validate the skill pair for X". Not for auditing a single SKILL.md — route to `/build:check-skill`. Not for re-distilling a stale principles doc — route to `/build:build-skill-pair`.
testing
Audit a root-level resolver — verify AGENTS.md pointer, managed-region integrity, filing-table coverage against disk, context-table actionability, and trigger-eval pass rate. Use when the user wants to "audit a resolver", "validate routing table", or "find dark capabilities".