ci: let private callers select a runner for the shared workflows - #125
ci: let private callers select a runner for the shared workflows#125chrisdpurcell wants to merge 1 commit into
Conversation
The four reusable workflows hardcode `runs-on: ubuntu-latest` with no way for a caller to override it. Because a reusable workflow bills the CALLER, every private consumer burns GitHub-hosted minutes on these gates and has no opt-out. Across the fleet that is 35 jobs in 12 private repositories, against a 2,000-minute monthly org allowance that was exhausted on 2026-08-04. Each workflow gains an optional `runner-labels` input defaulting to the empty string, so `runs-on` falls through to `ubuntu-latest` for every caller that does not set it. All 50 caller jobs currently pinning @v5 are therefore unaffected until each opts in deliberately. This attaches no self-hosted runner to this repository. A reusable workflow's runner is allocated from the caller's context, so a private caller passing self-hosted labels gets its own organization's runners. This repository is PUBLIC and must stay on GitHub-hosted runners — runner groups set allows_public_repositories=false, and self-hosted runners on a public repository would let fork pull requests execute untrusted code on the host. On this repo's own push and pull_request events the `inputs` context is empty, so the expression selects the hosted runner exactly as before. Callers must pass a JSON array string, e.g. with: runner-labels: '["self-hosted","linux","x64","l3digital-private"]' `fromJSON` is what turns it back into the list `runs-on` expects; a bare label string would be read as a single label.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThe reusable format, Markdown lint, frontmatter validation, and specification validation workflows now accept optional JSON runner labels. They use caller-provided runners when labels are present and otherwise use ChangesReusable workflow runner selection
Estimated code review effort: 2 (Simple) | ~15 minutes Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Pull request overview
This PR updates the repository’s four reusable GitHub Actions workflows to let private same-organization callers choose their own runner labels (including self-hosted) while keeping this public repository’s direct push/PR behavior effectively unchanged (defaulting to ubuntu-latest).
Changes:
- Added an optional
runner-labelsworkflow input (string, default"") to each reusable workflow. - Switched each job’s
runs-onto an expression that selects either the caller-provided runner labels (parsed viafromJSON) or falls back toubuntu-latest. - Added inline documentation clarifying that runner allocation occurs in the caller’s context and does not attach runners to this public repo.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| .github/workflows/validate-specs.yml | Adds runner-labels input and uses it to parameterize runs-on for the specs gate. |
| .github/workflows/validate-markdown-frontmatter.yml | Adds runner-labels input and makes runs-on selectable for frontmatter validation. |
| .github/workflows/lint-markdown.yml | Adds runner-labels input and makes the markdownlint job runner configurable for callers. |
| .github/workflows/format.yml | Adds runner-labels input and makes the Prettier job runner configurable for callers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Triage: the change is right; the delivery mechanism is the blockerReview of the approach, plus what the red CI actually means. Noted that this is explicitly not for merge yet. The design is the correct fixPer the runner-ownership rule, a shared reusable workflow that hardcodes
The Why CI is red — and it is not this PR's logicAll four workflows edited here are installed payload resources, not hand-maintained files:
Editing the installed copy desynchronizes it from the version it declares. Released payloads are immutable, so this capability has to ship as new payload minors across all three standards, with the canonical bytes changed under One point worth raising before that cycle
Also relevant#33 and #110 want to edit these same payload-managed workflows for GitHub Action SHA bumps. They are natural passengers on this same payload cycle rather than three separate ones — this PR is the capability change that justifies cutting it. Automated triage; no code changed. |
Dependabot cannot produce a mergeable PR in this repository. Four root workflows are installed payload resources byte-locked to published package versions, so a bump to one desynchronizes it from the version it declares (CP-MODIFIED-MANAGED); released payloads are immutable, so the bump has to travel through a new payload minor instead. The Node pins fail differently: their versions are documented behaviorally in shipped package prose, so moving one silently invalidates guidance this repository publishes. All six open PRs (#33, #110, #111, #112, #113 and the #125 capability change) are blocked on that same producer/consumer split rather than on anything upstream. Each carries a triage comment explaining what it needs. Scoping Dependabot to unmanaged paths only was rejected: the ignore list would duplicate the lock's record of which paths are payload-managed, and would rot silently whenever a package claims or relinquishes one. Dependabot alerts stay enabled at the repository level. They raise no pull requests, and security updates were already disabled, so vulnerability visibility is unchanged. Replaces the config-shape assertion with an absence assertion so re-adding the file is a deliberate decision rather than an accident.
…uv 9 Supersedes the payload-coupled scope of PRs #125 and #33 for this package. The 1.9 self-hosted workflow adds the optional runner-labels workflow_call input allocated from the caller's context and advances astral-sh/setup-uv to 9.0.0 with prune-cache: true preserving the v8 cache behavior. 1.8 bytes are untouched and stay advertised; the root workflow keeps the 1.8 render until the release-prep reconcile, so the 1.8 root-parity assertion moves to a reviewed-pin assertion. The catalogs/5.toml default advance and family landing pages batch at release prep with the other v5.16.0 cuts.
Supersedes the payload-coupled scope of PR #125 for this package. The 1.7 self-hosted workflow adds the optional runner-labels input and the setup-uv 9.0.0 pin with prune-cache: true; both digest-history chains (payload known_content_digests and the provider self-host set) append the new sha256:52e058a3 generation so earlier consumers still authenticate. adopt.md documents runner-labels and gains the SL-STRUCTURE troubleshooting row for the new strict-lint gate. The root workflow keeps the released 1.6 render until the release-prep reconcile; catalog advance batches with the other v5.16.0 cuts. Aggregate sha256:2d012e3d.
…#114, #119) The documented markdownlint command now selects parent-repo tracked files only: git ls-files with :(glob) positives and :(glob,exclude) negations, literal paths prefixed with ':' (a bare '#'- or '!'-leading filename is otherwise parsed as a glob and silently dropped), and --no-globs so a consumer runner config cannot re-widen the selection — each part probed against markdownlint-cli2 0.23.2 in a nested-repo fixture, where CLI negations provably do not filter literal paths. The unbounded --fix recovery recipe is bounded the same way. The #119 unmatched-pattern hazard is documented with its flag and proven tolerated by both rendered forms. self-host-format.yml gains runner-labels, setup-node 7.0.0, and the prettier@3.9.6 enforced pin; self-host-lint-markdown.yml gains runner-labels (payload scope of PRs #110/#113/#125); prose re-cites 0.23.2. Root workflows keep released 1.12 bytes until the release-prep reconcile. Aggregate sha256:be1e0630.
|
Superseded in v5.16.0 with the payload work this change needed: the runner-labels input landed verbatim on all four reusable workflows through their owning package cuts — markdown-tooling 1.13 (format, lint-markdown; commit 7ccdbc9), markdown-frontmatter 1.9 (commit 6f8c793), project-spec 1.7 (commit 8a2d1b9's activation re-render, cut 8d809ca) — plus validate-specs via its managed render. The root workflows were re-rendered by the release reconcile at 8a2d1b9, and the caller-context comment from this PR ships with each payload. The v5 repoint happened with the release. |
Not for merge yet — opened for review. Requested as a draft-the-change task; no tag is cut and the floating
v5is untouched.Problem
The four reusable workflows here hardcode
runs-on: ubuntu-latestand expose no runner input. A reusable workflow bills the caller, so every private consumer burns GitHub-hosted minutes on these gates with no opt-out.A fleet audit found 35 such jobs across 12 private repositories. The org's 2,000-minute monthly allowance was exhausted on 2026-08-04, which stopped every hosted job org-wide — surfacing as "recent account payments have failed or your spending limit needs to be increased", wording that points at billing when nothing is unpaid.
Change
Each of the four workflows gains an optional
runner-labelsinput, defaulting to"":Callers opt in with a JSON array string:
fromJSONis what turns it back into the listruns-onexpects — a bare string would be read as a single label.This does not put self-hosted runners on a public repo
Worth stating plainly, because that would be a genuine security problem — fork PRs executing untrusted code on your own hardware.
Per GitHub's docs: "Called workflows that are owned by the same user or organization as the caller workflow can access self-hosted runners from the caller's context."
So:
push/pull_requestevents theinputscontext is empty, the expression falls through toubuntu-latest, and behavior is byte-identical to today.allows_public_repositories=false, so a public repo could not use the pool even if a workflow asked.Backward compatibility
The default preserves current behavior exactly. All 50 caller jobs currently pinning
@v5are unaffected until each one deliberately passes the input — so movingv5after merge would be a no-op for every existing consumer.Verification
runner-labelspresent withdefault: "",required: false.prettier@3.8.3 --checkclean on.github/workflows/*.yml.agent-session-data's Check gate andagent-configs' check/go/format now run ongh-runner-{1,2}inhomelab-private, all green.Not included
v5repoint — deliberate, pending your review.@v3ref oflint-markdown.ymlis unchanged;control-centeris the only consumer still on it and should bump to@v5instead.Summary by CodeRabbit