Skip to content

📖 [Docs]: add job-vs-step and action-vs-reusable-workflow guidance to the GitHub Actions standard#17

Merged
Marius Storhaug (MariusStorhaug) merged 9 commits into
mainfrom
docs/actions-composition-guidance
Jul 5, 2026
Merged

📖 [Docs]: add job-vs-step and action-vs-reusable-workflow guidance to the GitHub Actions standard#17
Marius Storhaug (MariusStorhaug) merged 9 commits into
mainfrom
docs/actions-composition-guidance

Conversation

@MariusStorhaug

Copy link
Copy Markdown
Member

What

Adds two decision-oriented sections to the GitHub Actions coding standard
(src/docs/Coding-Standards/GitHub-Actions.md), answering two questions that
were not previously covered:

Structure work into jobs and steps

When a step is enough and when a new job is warranted. Default to steps
within one job for sequential, state-sharing work; reach for a new job only when
you need one of:

  • parallelism for independent work,
  • an isolated permission boundary (least privilege),
  • a different runner image or a deployment environment,
  • a named status check that gates merge, or
  • a call to a reusable workflow.

It also spells out what a job costs — a fresh runner with no shared filesystem,
so data crosses a job boundary only through outputs or artifacts — so jobs are
not created where ordered steps would do.

Choose an action or a reusable workflow

When to package reuse as an action (a reusable unit of steps, composed
inside a job) versus a reusable workflow (a reusable unit of jobs — a whole
multi-job pipeline). Includes the anti-patterns (don't wrap a single task in a
reusable workflow; don't force a multi-job pipeline into one action) and
cross-links the shared discipline: SHA pinning, start-local-then-promote, and
passing secrets explicitly by name.

Placement

Both sections sit together after the security rules and before the authoring
mechanics (PowerShell glue, script extraction), giving a top-down flow:
security → structure decisions → authoring details.

Notes

  • Matches the file's existing voice, cross-reference style, and code-example
    conventions (SHA-pinned checkout, permissions: {} default-deny floor,
    PowerShell steps).
  • Validated locally: Test-DocumentationLink.ps1 (all links/anchors resolve),
    markdownlint-cli2 with the repo config (0 errors).

… the GitHub Actions standard

Add two decision sections to the GitHub Actions coding standard:

- "Structure work into jobs and steps" — when a step suffices and when a new
  job is warranted (parallelism, permission boundary, distinct runner or
  deployment environment, merge gating, calling a reusable workflow), with the
  cost of a job (fresh runner, no shared filesystem) called out.
- "Choose an action or a reusable workflow" — an action as a reusable unit of
  steps vs a reusable workflow as a reusable unit of jobs, when to reach for
  each, and cross-links to SHA pinning, local-then-promote, and explicit
  secrets passing.
@MariusStorhaug Marius Storhaug (MariusStorhaug) added the documentation Improvements or additions to documentation label Jul 5, 2026
…ons standard

Steps can now run concurrently within a single job (the background / wait /
wait-all / cancel / parallel keywords, GitHub Actions changelog 2026-06-25).
Reflect this in "Structure work into jobs and steps":

- Soften the "steps run in order" claim to "in order by default".
- Frame a job as the way to spread independent work across separate runners.
- Add a "Parallel steps are new and not yet a default" note: the four keywords,
  the patterns they enable, and guidance to prefer a separate job for ordinary
  parallelism and confirm toolchain (actionlint/zizmor) support before relying
  on the feature.
…valid YAML

The "Distinguish vars from secrets" example packed a Correct and an Avoid snippet
into one fenced block, repeating the top-level `jobs:` key — which is not a valid
YAML document (duplicate mapping key, and rejected by GitHub Actions / yamllint).
Split it into two fenced blocks so the snippet parses on its own.

Validated every fenced example in the standard: all 13 YAML blocks parse under a
strict duplicate-key-aware loader, and both bash blocks pass `bash -n`. The one
remaining non-code block is an illustrative directory tree (text).

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR extends the GitHub Actions coding standard with decision-oriented guidance to help authors choose (1) when to structure work as steps vs separate jobs and (2) when to package reuse as an action vs a reusable workflow, placed in the document’s security → structure → authoring flow.

Changes:

  • Adds a new “Structure work into jobs and steps” section, including tradeoffs of job boundaries and an example showing permission isolation via a separate job.
  • Adds a new “Choose an action or a reusable workflow” section clarifying the appropriate reuse unit and common anti-patterns.
  • Fixes the secrets: inherit discussion formatting by separating the “Correct” vs “Avoid” YAML examples into distinct fenced blocks.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

The standard's own "Pin the runner and name everything" rule asks for a `name:`
on every job. The action/reusable-workflow example's `docs` and `ci` jobs were
unnamed — unlike every other example that shows a job with steps (the job/step,
logging, and status-check examples all name their jobs). Add `name: Publish docs`
and `name: CI` so the example follows the standard it documents.

Audited every fenced example against the standard: SHA-pinned actions, pinned
runners (ubuntu-24.04), least-privilege permissions, and secrets-by-name all hold
in the Correct/neutral snippets; the only unpinned action, `secrets: inherit`,
and inline-untrusted-input appear solely in the labelled Avoid snippets.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 3 comments.

Comment thread src/docs/Coding-Standards/GitHub-Actions.md
Comment thread src/docs/Coding-Standards/GitHub-Actions.md
Comment thread src/docs/Coding-Standards/GitHub-Actions.md
Strengthen the naming rule from "every job and every non-trivial step" to every
job AND every step, with the rationale: the name in the code is the label shown
in the portal (traceability / code-portal parity); it is a stable handle that
does not drift when the underlying `run`/`uses` changes; it names a failure by
intent rather than by a decoded command line; and — because we pin actions to
full SHAs — an unnamed action step would otherwise wear its 40-character SHA as
its UI label.

Add a companion rule: separate each job and each step with a single blank line.

Apply both across every example that contains steps (pinning, untrusted-input,
job/step, action/reusable-workflow, logging report, status check). Verified: all
15 code examples still parse, every step now starts with `- name:`, markdownlint
clean, links resolve.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.

Comment thread src/docs/Coding-Standards/GitHub-Actions.md Outdated
The 'Choose an action or a reusable workflow' summary said both are 'consumed the same disciplined way: pinned by full commit SHA', which over-generalised: a local action or workflow referenced by path (./.github/...) runs at the checked-out commit and is not SHA-pinned — only contradicting the local-action examples. Reframe as a lifecycle: start local (by path), promote to a standalone repo once a second consumer appears, and only then consume it by full commit SHA like any third-party dependency.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Comment thread src/docs/Coding-Standards/GitHub-Actions.md Outdated
Comment thread src/docs/Coding-Standards/GitHub-Actions.md Outdated
Copilot review: the intro said 'four workflow keywords' but the list names five distinct keys (background, wait, wait-all, cancel, parallel), and the toolchain caveat named only a three-key subset. Drop the count ('new workflow keywords') and make the caveat refer to 'the new keywords' so it covers all of them.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

PowerShell is the standard's default glue language, but the three examples in
"Build in logging and diagnostics" used bash `echo` — contradicting that rule,
and each one also contradicted its own bullet (which advocates the PowerShell
helpers). Convert them:

- grouped logging -> `LogGroup 'phase' { ... }`
- annotations -> `Write-GitHubNotice` / `Write-GitHubError` (which escape dynamic
  data for you, so the manual %25/%0D/%0A note stays in prose but drops from code)
- step summary -> `Set-GitHubStepSummary` with the PSModule `Markdown` module
  (`Heading`, `Table`, `Details`)

Verified helper signatures against the installed PSModule `GitHub` module
(v0.43.1) and the `Markdown` module's `Heading`/`Table`/`Details` aliases; both
PowerShell blocks pass the PowerShell language parser, and no `echo`/`shell: bash`
examples remain.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.

Comment thread src/docs/Coding-Standards/GitHub-Actions.md Outdated
Comment thread src/docs/Coding-Standards/GitHub-Actions.md Outdated
Copilot review: the "Never expand untrusted input inline" example still used bash
(`echo`, `$TITLE`) with no `shell:`, so a reader pasting it into a pwsh step would
find `$TITLE` empty. Set `shell: pwsh` and use `Write-Host $env:TITLE` (safe, the
value arrives as an env var) versus an inline expansion of the untrusted title
(unsafe). Now every example step declares `shell: pwsh`; the only remaining "echo"
is the prose "never echo a secret".

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.

@MariusStorhaug Marius Storhaug (MariusStorhaug) merged commit 81edaa6 into main Jul 5, 2026
20 checks passed
@MariusStorhaug Marius Storhaug (MariusStorhaug) deleted the docs/actions-composition-guidance branch July 5, 2026 23:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants