Skip to content

Commit 40859d6

Browse files
📖 [Docs]: Standardize separating reusable workflows from their action library (#44)
## What & why There was no standard for how to split a **reusable workflow** from the actions only it consumes. This documents it. A shared reusable workflow loads only its *own* file into the caller's run — its repository is not checked out — so a `./`-relative action reference resolves against the **caller's** checkout and breaks. A shared reusable workflow must therefore reference actions by full `owner/repo/path@<sha>`, which dictates where those actions have to live. ## Changes - **`Coding-Standards/GitHub-Actions.md`** — new section *"Separate a reusable workflow from the actions it consumes"* (right after *"Choose an action or a reusable workflow"*): - why a shared reusable workflow ships only its own file, and the contrast that a **composite** action's `./` resolves within its *own* repo at the same ref (the asymmetry that lets a set of actions ship together); - the two cross-repo homes — a **standalone action repo** (consumed on its own) vs an **action-library repo** (a cohesive set built, tested, and released **together as one package**), tied into the [Repository Segmentation](../Ways-of-Working/Repository-Segmentation.md) shared-lifecycle principle; - a pointer bullet added to *"Start local; promote when it is reused."* ## Companion Companion to **AI-Platform/ai-platform#411**, which adds the same standard to the mirrored coding standards (and a matching rule in that repo's `repository-segmentation.md`). MSXOrg's `Repository-Segmentation.md` already carries the shared-lifecycle principle, so only the coding standard changes here. ## Validation - markdownlint (repo config) clean. - New section and all anchor targets (in-file and the cross-file segmentation anchor) verified to resolve.
1 parent 1662610 commit 40859d6

1 file changed

Lines changed: 104 additions & 0 deletions

File tree

‎src/docs/Coding-Standards/GitHub-Actions.md‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,106 @@ jobs:
359359
PROPAGATION_TOKEN: ${{ secrets.PROPAGATION_TOKEN }} # explicit, by name
360360
```
361361

362+
## Separate a reusable workflow from the actions it consumes
363+
364+
Choosing a reusable workflow has a consequence for the actions it runs. The
365+
workflow is a **process** — it stitches jobs into an order — and the actions are
366+
the **building blocks** it calls. Once the workflow is shared across
367+
repositories, GitHub's reference resolution forces the two apart, and the split
368+
pays off in versioning and reuse.
369+
370+
### A shared reusable workflow ships only workflow files
371+
372+
When one repository calls another's reusable workflow
373+
by full path, GitHub loads that workflow file into the caller's run:
374+
375+
```yaml
376+
jobs:
377+
call-shared-process:
378+
uses: OWNER/REPO/.github/workflows/name.yml@<sha>
379+
```
380+
381+
If that workflow calls another reusable workflow, GitHub loads the nested
382+
workflow file too. It still does **not** check out the reusable workflow's
383+
repository. A local action reference inside the workflow therefore resolves
384+
against the **caller's** checkout, not the workflow's own repository, and finds
385+
the wrong action or none at all:
386+
387+
```yaml
388+
steps:
389+
- name: Run a local action
390+
uses: ./.github/actions/some-action
391+
```
392+
393+
A same-repository caller may still name the workflow itself by a local path; the
394+
constraint is on the actions the workflow reaches for.
395+
396+
- **Reference every action from a shared reusable workflow by full path** —
397+
`OWNER/REPO/path@<sha>`, which resolves the same way regardless of which
398+
repository is checked out. Pin it by SHA like any other dependency (see
399+
[Pin every action to a full commit SHA](#pin-every-action-to-a-full-commit-sha)).
400+
- **A composite action may still call a sibling with `./`.** Inside a composite
401+
action, `./` resolves within *that action's own repository at the same ref* —
402+
the opposite of the workflow case — so colocated actions call each other with
403+
`./` and no pin. This asymmetry is what lets a set of actions live and ship
404+
together.
405+
- **Self-checkout is the only escape hatch, and a poor foundation.** A reusable
406+
workflow *can* check its own repository out at runtime — without caller input —
407+
through the `job.workflow_repository` / `job.workflow_ref` / `job.workflow_sha`
408+
contexts (`actions/checkout` with `repository: ${{ job.workflow_repository }}`,
409+
`ref: ${{ job.workflow_sha }}`). But those contexts are **not available on
410+
GitHub Enterprise Server**, the checkout is extra machinery that must not
411+
clobber the caller's workspace, and it ties the actions to the workflow's own
412+
commit — so an action others also consume gets no version line of its own.
413+
Treat it as a last resort, not the pattern.
414+
415+
### Why the actions need their own versioned repository
416+
417+
Two lifecycle needs make a **separately versioned** action repository — not
418+
colocated local actions — the sound choice:
419+
420+
- **Development must test the branch, not the last release.** While you build the
421+
workflow, you want it to run the action versions *on the branch you are
422+
developing*. A `./` local action does not resolve in a shared reusable workflow
423+
at all, and pinning `OWNER/REPO/action@<released-sha>` runs stale code — so give
424+
the actions their own repository and point the workflow's pin at that
425+
repository's **branch or commit** to test branch against branch.
426+
- **A release cannot pin its actions to itself.** The action versions a released
427+
workflow runs must be exactly those of that release. But the workflow's tag is
428+
cut *after* the merge to its default branch, so the workflow file can never pin
429+
a colocated action to its own release commit — that commit does not exist when
430+
the file is written. An independently versioned repository has a commit of its
431+
own that the workflow pins by SHA, locking the release to an exact set of
432+
actions.
433+
434+
### Give the actions a home that matches their reuse
435+
436+
Use the smallest home that matches the reuse boundary and lifecycle:
437+
438+
- **Use a local action** when the logic is used by one repository. Keep it under
439+
`.github/actions/<action-name>/` and call it by `./` so the workflow tests the
440+
action at the checked-out commit.
441+
- **Use a standalone action repository** when one action is consumed **on its
442+
own** by other repositories — its own responsibility, its own version line.
443+
This is the promotion path in [Start local; promote when it is
444+
reused](#start-local-promote-when-it-is-reused).
445+
- **Use an action-library repository** when several actions are built, tested,
446+
and released together as one package. Their lifecycle is shared — changing one
447+
re-releases the set — so
448+
[they belong in one repository](../Ways-of-Working/Repository-Segmentation.md#share-a-repository-only-when-the-development-lifecycle-is-shared),
449+
not a repository each: several top-level composite actions, one test suite, one
450+
release, one SHA to pin.
451+
- **Use a reusable-workflow repository** when the thing reused is the *process*:
452+
a job graph, permissions contract, environment gates, and ordering that several
453+
repositories should run the same way.
454+
455+
A shared reusable workflow cannot reach a local action. When that workflow needs
456+
non-trivial actions, keep the workflow and the action library in **separate
457+
repositories**: the workflow repository — the *process* — pins the action-library
458+
repository — the *building blocks* — by SHA. A change then flows outward as a
459+
deliberate bump at each hop, so every layer upgrades on a reviewed pull request
460+
rather than silently.
461+
362462
## Default to PowerShell as the glue language
363463

364464
Between the declarative steps, a workflow always has some glue to run — reading a
@@ -500,6 +600,10 @@ appears.
500600
[Pin every action to a full commit SHA](#pin-every-action-to-a-full-commit-sha)).
501601
Do not reach for a separate repo preemptively — the cost of a shared release
502602
surface is only worth paying once there is a second consumer.
603+
- **A reusable workflow's actions are the exception** — a shared reusable
604+
workflow cannot reach a local action, and a cohesive set is better kept in one
605+
action library than a repository each. See [Separate a reusable workflow from
606+
the actions it consumes](#separate-a-reusable-workflow-from-the-actions-it-consumes).
503607

504608
### Move the script into its own file
505609

0 commit comments

Comments
 (0)