From 82a12189252fc017e1547caef998d6b74c296fd6 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Tue, 14 Jul 2026 12:37:41 +0200 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Standardize=20sepa?= =?UTF-8?q?rating=20reusable=20workflows=20from=20their=20action=20library?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A shared reusable workflow ships only its own file to the caller, so a co-located ./ action cannot be resolved. Extend the GitHub Actions standard's action-vs-workflow guidance: reference actions from a shared reusable workflow by full owner/repo/path@sha, and house them either in a standalone repo (consumed alone) or a cohesive action-library repo (a set built/tested/released together, one SHA to pin) kept separate from the workflow that stitches them into a process. Ties into the Repository Segmentation shared-lifecycle principle. Companion to AI-Platform/ai-platform PR #411. --- src/docs/Coding-Standards/GitHub-Actions.md | 56 +++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/src/docs/Coding-Standards/GitHub-Actions.md b/src/docs/Coding-Standards/GitHub-Actions.md index 5872a57..cf68323 100644 --- a/src/docs/Coding-Standards/GitHub-Actions.md +++ b/src/docs/Coding-Standards/GitHub-Actions.md @@ -359,6 +359,58 @@ jobs: PROPAGATION_TOKEN: ${{ secrets.PROPAGATION_TOKEN }} # explicit, by name ``` +## Separate a reusable workflow from the actions it consumes + +Choosing a reusable workflow has a consequence for the actions it runs. The +workflow is a **process** — it stitches jobs into an order — and the actions are +the **building blocks** it calls. Once the workflow is shared across +repositories, GitHub's reference resolution forces the two apart, and the split +pays off in versioning and reuse. + +### A shared reusable workflow ships only its own file + +When one repository calls another's reusable workflow +(`uses: OWNER/REPO/.github/workflows/name.yml@`), GitHub loads **only that +one workflow file** into the caller's run — the reusable workflow's repository is +*not* checked out. A `./`-relative action reference inside it therefore resolves +against the **caller's** checkout, not the workflow's own repository, and finds +the wrong action or none at all. (A same-repository caller may still name the +workflow itself by a local `./` path; the constraint is on the actions the +workflow reaches for.) + +- **Reference every action from a shared reusable workflow by full path** — + `OWNER/REPO/path@`, which resolves the same way regardless of which + repository is checked out. Pin it by SHA like any other dependency (see + [Pin every action to a full commit SHA](#pin-every-action-to-a-full-commit-sha)). +- **A composite action may still call a sibling with `./`.** Inside a composite + action, `./` resolves within *that action's own repository at the same ref* — + the opposite of the workflow case — so co-located actions call each other with + `./` and no pin. This asymmetry is what lets a set of actions live and ship + together. + +### Give the actions a home that matches their reuse + +A shared reusable workflow cannot reach a local action, so its actions live in +one of two cross-repository homes — chosen, as always, by +[single responsibility and shared lifecycle](../Ways-of-Working/Repository-Segmentation.md): + +- **A standalone action repository** when the action is consumed **on its own** + by other repositories — its own responsibility, its own version line. This is + the promotion path in [Start local; promote when it is + reused](#start-local-promote-when-it-is-reused). +- **An action-library repository** when a reusable workflow needs **several + actions built, tested, and released together as one set**. Their lifecycle is + shared — changing one re-releases the set — so + [they belong in one repository](../Ways-of-Working/Repository-Segmentation.md#share-a-repository-only-when-the-development-lifecycle-is-shared), + not a repository each: several top-level composite actions, one test suite, one + release, one SHA to pin. It is a package of actions, consumed like any other + dependency. + +Keep the workflow and the library in **separate repositories**: the workflow +repository — the *process* — pins the action-library repository — the *building +blocks* — by SHA. A change then flows outward as a deliberate bump at each hop, +so every layer upgrades on a reviewed pull request rather than silently. + ## Default to PowerShell as the glue language Between the declarative steps, a workflow always has some glue to run — reading a @@ -500,6 +552,10 @@ appears. [Pin every action to a full commit SHA](#pin-every-action-to-a-full-commit-sha)). Do not reach for a separate repo preemptively — the cost of a shared release surface is only worth paying once there is a second consumer. +- **A reusable workflow's actions are the exception** — a shared reusable + workflow cannot reach a local action, and a cohesive set is better kept in one + action library than a repository each. See [Separate a reusable workflow from + the actions it consumes](#separate-a-reusable-workflow-from-the-actions-it-consumes). ### Move the script into its own file From 59174c4c6e67ff177e71b9fb38d0bae8c2dfaa26 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Tue, 14 Jul 2026 13:42:32 +0200 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=93=96=20[Docs]:=20Track=20the=20reus?= =?UTF-8?q?able-workflow=20self-checkout=20constraint=20and=20the=20two=20?= =?UTF-8?q?lifecycle=20reasons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the job.workflow_* self-checkout escape hatch (and why it is a poor foundation: GHES-unavailable, extra machinery, no independent action version line), plus the two lifecycle reasons a separately versioned action repo beats co-located local actions (development tests the branch; a release cannot pin actions to its own not-yet-existing commit). Companion to AI-Platform/ai-platform PR #411. --- src/docs/Coding-Standards/GitHub-Actions.md | 28 +++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/docs/Coding-Standards/GitHub-Actions.md b/src/docs/Coding-Standards/GitHub-Actions.md index cf68323..390a26f 100644 --- a/src/docs/Coding-Standards/GitHub-Actions.md +++ b/src/docs/Coding-Standards/GitHub-Actions.md @@ -387,6 +387,34 @@ workflow reaches for.) the opposite of the workflow case — so co-located actions call each other with `./` and no pin. This asymmetry is what lets a set of actions live and ship together. +- **Self-checkout is the only escape hatch, and a poor foundation.** A reusable + workflow *can* check its own repository out at runtime — without caller input — + through the `job.workflow_repository` / `job.workflow_ref` / `job.workflow_sha` + contexts (`actions/checkout` with `repository: ${{ job.workflow_repository }}`, + `ref: ${{ job.workflow_sha }}`). But those contexts are **not available on + GitHub Enterprise Server**, the checkout is extra machinery that must not + clobber the caller's workspace, and it ties the actions to the workflow's own + commit — so an action others also consume gets no version line of its own. + Treat it as a last resort, not the pattern. + +### Why the actions need their own versioned repository + +Two lifecycle needs make a **separately versioned** action repository — not +co-located local actions — the sound choice: + +- **Development must test the branch, not the last release.** While you build the + workflow, you want it to run the action versions *on the branch you are + developing*. A `./` local action does not resolve in a shared reusable workflow + at all, and pinning `OWNER/REPO/action@` runs stale code — so give + the actions their own repository and point the workflow's pin at that + repository's **branch or commit** to test branch against branch. +- **A release cannot pin its actions to itself.** The action versions a released + workflow runs must be exactly those of that release. But the workflow's tag is + cut *after* the merge to its default branch, so the workflow file can never pin + a co-located action to its own release commit — that commit does not exist when + the file is written. An independently versioned repository has a commit of its + own that the workflow pins by SHA, locking the release to an exact set of + actions. ### Give the actions a home that matches their reuse From c81595aa48dac6e67226e65c55cad02c53d2b850 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 16 Jul 2026 21:47:53 +0200 Subject: [PATCH 3/5] docs: clarify reusable workflow action homes --- src/docs/Coding-Standards/GitHub-Actions.md | 43 ++++++++++++--------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/src/docs/Coding-Standards/GitHub-Actions.md b/src/docs/Coding-Standards/GitHub-Actions.md index 390a26f..4ac712d 100644 --- a/src/docs/Coding-Standards/GitHub-Actions.md +++ b/src/docs/Coding-Standards/GitHub-Actions.md @@ -384,7 +384,7 @@ workflow reaches for.) [Pin every action to a full commit SHA](#pin-every-action-to-a-full-commit-sha)). - **A composite action may still call a sibling with `./`.** Inside a composite action, `./` resolves within *that action's own repository at the same ref* — - the opposite of the workflow case — so co-located actions call each other with + the opposite of the workflow case — so colocated actions call each other with `./` and no pin. This asymmetry is what lets a set of actions live and ship together. - **Self-checkout is the only escape hatch, and a poor foundation.** A reusable @@ -400,7 +400,7 @@ workflow reaches for.) ### Why the actions need their own versioned repository Two lifecycle needs make a **separately versioned** action repository — not -co-located local actions — the sound choice: +colocated local actions — the sound choice: - **Development must test the branch, not the last release.** While you build the workflow, you want it to run the action versions *on the branch you are @@ -411,33 +411,38 @@ co-located local actions — the sound choice: - **A release cannot pin its actions to itself.** The action versions a released workflow runs must be exactly those of that release. But the workflow's tag is cut *after* the merge to its default branch, so the workflow file can never pin - a co-located action to its own release commit — that commit does not exist when + a colocated action to its own release commit — that commit does not exist when the file is written. An independently versioned repository has a commit of its own that the workflow pins by SHA, locking the release to an exact set of actions. ### Give the actions a home that matches their reuse -A shared reusable workflow cannot reach a local action, so its actions live in -one of two cross-repository homes — chosen, as always, by -[single responsibility and shared lifecycle](../Ways-of-Working/Repository-Segmentation.md): +Use the smallest home that matches the reuse boundary and lifecycle: -- **A standalone action repository** when the action is consumed **on its own** - by other repositories — its own responsibility, its own version line. This is - the promotion path in [Start local; promote when it is +- **Use a local action** when the logic is used by one repository. Keep it under + `.github/actions//` and call it by `./` so the workflow tests the + action at the checked-out commit. +- **Use a standalone action repository** when one action is consumed **on its + own** by other repositories — its own responsibility, its own version line. + This is the promotion path in [Start local; promote when it is reused](#start-local-promote-when-it-is-reused). -- **An action-library repository** when a reusable workflow needs **several - actions built, tested, and released together as one set**. Their lifecycle is - shared — changing one re-releases the set — so +- **Use an action-library repository** when several actions are built, tested, + and released together as one package. Their lifecycle is shared — changing one + re-releases the set — so [they belong in one repository](../Ways-of-Working/Repository-Segmentation.md#share-a-repository-only-when-the-development-lifecycle-is-shared), not a repository each: several top-level composite actions, one test suite, one - release, one SHA to pin. It is a package of actions, consumed like any other - dependency. - -Keep the workflow and the library in **separate repositories**: the workflow -repository — the *process* — pins the action-library repository — the *building -blocks* — by SHA. A change then flows outward as a deliberate bump at each hop, -so every layer upgrades on a reviewed pull request rather than silently. + release, one SHA to pin. +- **Use a reusable-workflow repository** when the thing reused is the *process*: + a job graph, permissions contract, environment gates, and ordering that several + repositories should run the same way. + +A shared reusable workflow cannot reach a local action. When that workflow needs +non-trivial actions, keep the workflow and the action library in **separate +repositories**: the workflow repository — the *process* — pins the action-library +repository — the *building blocks* — by SHA. A change then flows outward as a +deliberate bump at each hop, so every layer upgrades on a reviewed pull request +rather than silently. ## Default to PowerShell as the glue language From 264df0253e473f955b9097df08e36252f262b556 Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 16 Jul 2026 21:54:27 +0200 Subject: [PATCH 4/5] docs: clarify nested reusable workflow loading --- src/docs/Coding-Standards/GitHub-Actions.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/docs/Coding-Standards/GitHub-Actions.md b/src/docs/Coding-Standards/GitHub-Actions.md index 4ac712d..f27d111 100644 --- a/src/docs/Coding-Standards/GitHub-Actions.md +++ b/src/docs/Coding-Standards/GitHub-Actions.md @@ -367,16 +367,17 @@ the **building blocks** it calls. Once the workflow is shared across repositories, GitHub's reference resolution forces the two apart, and the split pays off in versioning and reuse. -### A shared reusable workflow ships only its own file +### A shared reusable workflow ships only workflow files When one repository calls another's reusable workflow -(`uses: OWNER/REPO/.github/workflows/name.yml@`), GitHub loads **only that -one workflow file** into the caller's run — the reusable workflow's repository is -*not* checked out. A `./`-relative action reference inside it therefore resolves -against the **caller's** checkout, not the workflow's own repository, and finds -the wrong action or none at all. (A same-repository caller may still name the -workflow itself by a local `./` path; the constraint is on the actions the -workflow reaches for.) +(`uses: OWNER/REPO/.github/workflows/name.yml@`), GitHub loads that workflow +file into the caller's run. If that workflow calls another reusable workflow, +GitHub loads the nested workflow file too. It still does **not** check out the +reusable workflow's repository. A `./`-relative action reference inside the +workflow therefore resolves against the **caller's** checkout, not the workflow's +own repository, and finds the wrong action or none at all. (A same-repository +caller may still name the workflow itself by a local `./` path; the constraint is +on the actions the workflow reaches for.) - **Reference every action from a shared reusable workflow by full path** — `OWNER/REPO/path@`, which resolves the same way regardless of which From 0b3e7dc1fe6027ecdd2c5ef82f5cfcb1aaaf944a Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 16 Jul 2026 22:07:03 +0200 Subject: [PATCH 5/5] docs: use fenced workflow examples --- src/docs/Coding-Standards/GitHub-Actions.md | 30 +++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/src/docs/Coding-Standards/GitHub-Actions.md b/src/docs/Coding-Standards/GitHub-Actions.md index f27d111..e87d4ec 100644 --- a/src/docs/Coding-Standards/GitHub-Actions.md +++ b/src/docs/Coding-Standards/GitHub-Actions.md @@ -370,14 +370,28 @@ pays off in versioning and reuse. ### A shared reusable workflow ships only workflow files When one repository calls another's reusable workflow -(`uses: OWNER/REPO/.github/workflows/name.yml@`), GitHub loads that workflow -file into the caller's run. If that workflow calls another reusable workflow, -GitHub loads the nested workflow file too. It still does **not** check out the -reusable workflow's repository. A `./`-relative action reference inside the -workflow therefore resolves against the **caller's** checkout, not the workflow's -own repository, and finds the wrong action or none at all. (A same-repository -caller may still name the workflow itself by a local `./` path; the constraint is -on the actions the workflow reaches for.) +by full path, GitHub loads that workflow file into the caller's run: + +```yaml +jobs: + call-shared-process: + uses: OWNER/REPO/.github/workflows/name.yml@ +``` + +If that workflow calls another reusable workflow, GitHub loads the nested +workflow file too. It still does **not** check out the reusable workflow's +repository. A local action reference inside the workflow therefore resolves +against the **caller's** checkout, not the workflow's own repository, and finds +the wrong action or none at all: + +```yaml +steps: + - name: Run a local action + uses: ./.github/actions/some-action +``` + +A same-repository caller may still name the workflow itself by a local path; the +constraint is on the actions the workflow reaches for. - **Reference every action from a shared reusable workflow by full path** — `OWNER/REPO/path@`, which resolves the same way regardless of which