Skip to content

ci: replace ubuntu-slim fallback with free ubuntu-24.04#141

Merged
kyle-sexton merged 1 commit into
mainfrom
ci/remove-ubuntu-slim-fallback
Jul 18, 2026
Merged

ci: replace ubuntu-slim fallback with free ubuntu-24.04#141
kyle-sexton merged 1 commit into
mainfrom
ci/remove-ubuntu-slim-fallback

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

What

Swap the paid ubuntu-slim hosted fallback for the standard, free
ubuntu-24.04 runner across the selector job and the three fail-closed
selector-result reporters:

  • .github/workflows/select-runner.yml — selector job fallback
  • .github/workflows/semantic-pr.ymlpr-title reporter fallback
  • .github/workflows/do-not-merge-gate.ymldo-not-merge reporter fallback
  • .github/workflows/pr-issue-linkage.ymlpr-issue-linkage reporter fallback

Also updated:

  • the adjacent cost-framing comments (which called the fallback the
    "least-expensive/inexpensive hosted" runner) so they state the honest
    new behavior;
  • the .cjs contract tests that pin the literal
    (select-runner.test.cjs, semantic-pr.test.cjs);
  • the three README.md lines that describe this fallback/reporter
    behavior, so the docs land with the change they describe.

Why

ubuntu-slim is billed even on public repositories. This public repo
burned 6,906 minutes for $13.44 net in July (observed in org billing).
ubuntu-24.04 is free on public repos and quota-covered on private
ones, so the fallback carries no minutes cost while preserving the
fail-closed reporting contract: a non-success selector outcome still
materializes the required check on a hosted runner, and successful calls
still consume exactly the caller-selected runner.

No consumer contract change — same inputs, permissions, and secrets; only
the internal hosted fallback label changes.

Verification

  • node --test on both .cjs suites: 104 pass, 0 fail.

  • Sanity grep over the whole worktree is clean:

    $ git grep -n "ubuntu-slim"
    (zero matches)
    

Related

No linked issue. Part of the org-wide Actions zero-spend effort;
companion PR: melodic-software/medley#1570 (parks the paid hosted lanes
there).

🤖 Generated with Claude Code

## What

Swap the `ubuntu-slim` hosted fallback for the standard `ubuntu-24.04`
runner in the selector job and the three fail-closed selector-result
reporters (`select-runner`, `semantic-pr`, `do-not-merge-gate`,
`pr-issue-linkage`), update the adjacent cost-framing comments and the
`.cjs` contract tests that pin the literal, and reconcile the three
README lines that describe this fallback/reporter behavior so the docs
land with the change they describe.

## Why

`ubuntu-slim` is billed even on public repositories: this public repo
burned 6,906 minutes for $13.44 net in July (observed in org billing).
`ubuntu-24.04` is free on public repos and quota-covered on private
ones, so the fallback carries no minutes cost while preserving the
fail-closed reporting contract (a non-success selector outcome still
materializes the required check on a hosted runner, and successful
calls still consume exactly the caller-selected runner).

No consumer contract change: same inputs, permissions, and secrets;
only the internal hosted fallback label changes. All 104 node:test
assertions pass; `git grep ubuntu-slim` over the whole worktree is now
clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015hTQ64MDYGvqVUEPPWQV6Q
@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 19s —— View job


Code Review

  • Gather PR diff and context
  • Review workflow YAML changes
  • Review test file changes
  • Review README changes
  • Post final review

Verdict: Looks good — approve. The change is mechanically correct, internally consistent, well-motivated, and does not touch any security-sensitive surface.


Workflow YAML changes

All four runs-on lines are updated correctly and consistently:

File Before After
select-runner.yml:69 ubuntu-slim ubuntu-24.04
semantic-pr.yml:96 ubuntu-slim ubuntu-24.04
do-not-merge-gate.yml:42 ubuntu-slim ubuntu-24.04
pr-issue-linkage.yml:46 ubuntu-slim ubuntu-24.04

One detail worth noting: select-runner.cjs:3 already defines DEFAULT_HOSTED_RUNNER = "ubuntu-24.04", meaning the JS selector logic was already returning ubuntu-24.04 as the hosted fallback for consumers. The YAML literals being fixed here are the selector job's own runs-on and the three reporter jobs' runs-on — a genuine inconsistency that's now resolved.

Fail-closed contract preserved. The ternary pattern prerequisite-result == 'success' && inputs.runner || 'ubuntu-24.04' in the three reporter workflows is unchanged in structure; only the fallback label changed. The contract that a non-success selector result still materialises the required check on a hosted runner is intact.

Adjacent comments updated correctly. do-not-merge-gate.yml, select-runner.yml, and semantic-pr.yml all had cost-framing comments; all three were updated. pr-issue-linkage.yml's adjacent comment is about job ID naming convention (not cost), so no update was needed there — correct omission.

CLAUDE.md security rules. No event triggers, permissions, secrets scope, or checkout ordering changed. The pull_request-only tripwire in claude-review is untouched. Nothing here broadens the CLAUDE_CODE_OAUTH_TOKEN scope or downgrades token isolation. ✓

Test changes

select-runner.test.cjs updates the regex that asserts the literal runs-on expression in the workflow file. semantic-pr.test.cjs updates both the regex assertion and the runnerFor helper that mirrors the ternary logic — the helper correctly validates that any non-success result (including empty string) resolves to ubuntu-24.04 rather than the caller-supplied runner.

One pre-existing observation (not introduced here): pr-issue-linkage.test.cjs and the absence of a do-not-merge-gate.test.cjs mean there are no contract tests asserting the literal runs-on in those two reporter workflows. That gap existed before this PR and isn't a blocker, but it's the reason the ubuntu-slim inconsistency in those files wasn't caught by tests earlier.

README changes

All three prose references to the old fallback label are updated accurately. The parenthetical "(free on public repos, quota-covered on private)" is technically correct: ubuntu-24.04 consumes included GitHub-provided minutes on private repos while costing nothing on public ones.

One minor wording note — the README at the updated line says the fallback is "free on public repos" without caveating that ubuntu-24.04 starts up slightly slower than ubuntu-slim (larger image). For the use case here (shell-only selector/reporter jobs with 2- and 15-minute timeouts) this is completely negligible and doesn't warrant a comment change.

Summary

Clean, well-scoped change. Correct everywhere, consistent everywhere, no security-surface changes, tests updated, docs land with the change. Ready to merge.

@kyle-sexton
kyle-sexton merged commit 0e3e9fc into main Jul 18, 2026
40 of 41 checks passed
@kyle-sexton
kyle-sexton deleted the ci/remove-ubuntu-slim-fallback branch July 18, 2026 05:47
kyle-sexton added a commit to melodic-software/standards that referenced this pull request Jul 18, 2026
## What

Two changes to the managed `runner-policy` component that stop a paid
hosted label from silently becoming the governed fallback:

1. **Remove `ubuntu-slim` from `approvedHostedRunnerLabels`.** It can no
longer be an explicit `runs-on` target, a matrix value, or the governed
   fallback default. `ubuntu-24.04` is the sanctioned free fallback; the
   `ubuntu-slim` tier was eliminated (billed even on public repos).
2. **Add a `fallbackLabelAllowlist` policy field** (default
   `["ubuntu-24.04"]`) and require `governedReusableRunnerInput.default`
   to be a member — a gate strictly tighter than
   `approvedHostedRunnerLabels`. A label can be an approved explicit
target yet still be barred from becoming the silent recovery fallback.

## Why this shape (and not the originally-scoped per-workflow rule)

The initial framing was a per-workflow finding rejecting any
non-allowlisted
literal in the hosted-fallback arm of a selector `runs-on`. The
validator's
architecture does not have that gap: the selector-output arm
(`${{ needs.X.outputs.runner || '<label>' }}`) is already pinned by
`routeStatus` to exactly `governedReusableRunnerInput.default`, and the
ternary arm (`… && '<fleet>' || '<label>'`) is rejected wholesale as
unresolvable dynamic routing. A consumer cannot write `|| 'ubuntu-slim'`
unless the policy default itself is `ubuntu-slim`. The real
silent-return
path is therefore the **policy default**, validated only against
`approvedHostedRunnerLabels` (which still contained `ubuntu-slim`). This
PR
closes it at that single source, plus removes the label so bare
`runs-on: ubuntu-slim` also dies.

## Compatibility

- **Default allowlist does not break any consumer.** All consumer
selector
  fallbacks are `|| 'ubuntu-24.04'` (org-verified 2026-07-18) and
  `ubuntu-24.04` is the default, so it is in the default allowlist. No
  consumer `runner-policy.json` references `ubuntu-slim`.
- This repository's own workflows pass the audit under CI conditions
  (`CI_REPOSITORY_VISIBILITY=private`, owner evidence present): "Runner
  policy passed.", exit 0.

## schemaVersion

Not bumped. `schemaVersion` has no runtime semantics (never read by
`runner-policy.mjs`; it is a declarative `const` marker), no test
hardcodes
it, and prior schema field-additions (#140, #155) did not bump it.
`policy.json`
and `policy.schema.json` are `managed` and sync verbatim as one unit, so
no
consumer ships a hand-authored policy the new required field could
reject.

## Tests

`node --test components/runner-policy/runner-policy.test.mjs` — 213
pass, 0
fail (was 208; +5). New config-level cases, matching the real
`ConfigurationError` architecture rather than a per-workflow finding:

- required-field presence for `fallbackLabelAllowlist`;
- default not in the allowlist (approved `windows-2025`) rejects;
- `ubuntu-slim` default now rejects as not-approved (locks the removal);
- shipped `|| 'ubuntu-24.04'` selector route passes clean;
- allowlist extended to admit a second approved default passes clean.

Also updated the existing "selector recovery derives its literal
fallback"
test to extend `fallbackLabelAllowlist` alongside its `default`
override.

`lint:md`, `biome check`, and the runner-policy self-audit are green.

## Reconciliation checklist

- [x] Cross-doc reconciliation run. Updated `README.md` (fallback
passage)
and `THREAT-MODEL.md` (residual-risk bullet) to state the new allowlist
  bound; both are consistent additions, not restatements.
`conventions/review/ai-review-bot-composition.md` checked — its
"default"
  usage is bot-composition, unrelated; not contradicted.
- [x] No other doc in the catalog references the fallback default or the
  approved-label set in a way this change contradicts.

## Related

No linked issue: this PR delivers the fallback-allowlist guardrail
directly
without a tracking issue.

- melodic-software/ci-workflows#141 — companion
- melodic-software/medley#1570 — companion
- melodic-software/ci-workflows#143 — context for why the `ubuntu-slim`
  tier is dead

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_015hTQ64MDYGvqVUEPPWQV6Q

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit to melodic-software/ci-runner that referenced this pull request Jul 18, 2026
## What

Move the ci-workflows compatibility pin `c36e881` (v0.5.0) → `90f1c54`
(v0.6.1) across all 20 workflow references, the pin-metadata test
constants, and `release/dependencies.json`.

## Why

v0.5.0's `do-not-merge-gate` shells `gh` in its label refetch; v0.6.1
carries the github-script rewrite (melodic-software/ci-workflows#144)
plus the ubuntu-slim fallback removal
(melodic-software/ci-workflows#141). This repo's Product policy gate
requires every ci-workflows reference to move as one reviewed pin with a
canonical `@<sha> # vX.Y.Z` comment, so the org's narrow gate-only bump
pattern converges here as a full pin move instead.

## Verification

All 28 `.github/scripts` tests pass locally, including the pin-metadata
inventory (20 references, one SHA, one release). This PR's own
do-not-merge run resolves the bumped ref. `git grep c36e881` = 0.

v0.5.0..v0.6.1 caller impact reviewed: new `link-check` inputs all carry
defaults; fallback runner labels and gate internals changed inside the
reusables only. No caller contract changes.

## Related

No linked issue. Sibling bumps: melodic-software/provisioning#167,
melodic-software/claude-code-plugins#332, melodic-software/dotfiles#220;
gate fix melodic-software/ci-workflows#144.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01YZxBP1B8Hf7ZRaqDrP6ma9

---------

Co-authored-by: Claude Fable 5 (1M context) <noreply@anthropic.com>
kyle-sexton added a commit to melodic-software/standards that referenced this pull request Jul 18, 2026
)

Registers the remaining `ci-workflows@90f1c54` contract entries so the
full six-file gate-pin bumps (#189 and the
dotfiles/provisioning/claude-code-plugins companions) pass runner
policy: claude-review, link-check, semantic-pr, pr-issue-linkage,
zizmor, osv-scanner in `approvedReusableWorkflowContracts`, plus the
select-runner reference in
`approvedSelectorReferencesByRepositoryOwner`.

Review basis, per contract, against each entry's newest already-approved
SHA:

- claude-review, link-check, osv-scanner: byte-identical at 90f1c54.
- semantic-pr, pr-issue-linkage, select-runner: only the hosted fallback
label changes (retired `ubuntu-slim` → allowlisted `ubuntu-24.04`,
melodic-software/ci-workflows#141) plus comments.
- zizmor: version-pin default bump (v1.26.1 → v1.27.0) and curl timeout
hardening; input surface unchanged.

No input, secret, routing, or caller-permission surface changes
anywhere, so each entry copies its predecessor's reviewed body.
do-not-merge-gate@90f1c54 was already registered by #190. Component
tests updated for the new selector reference and pass (224/224).

No linked issue.

## Related

- #189 — full-scope standards pin bump this unblocks
- #190 — do-not-merge-gate@90f1c54 registration pattern
- melodic-software/ci-workflows#141 — ubuntu-slim retirement behind the
fallback-label diffs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01YZxBP1B8Hf7ZRaqDrP6ma9

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit to melodic-software/standards that referenced this pull request Jul 18, 2026
## What

Declare `visibility: "public"` in `.github/runner-policy.json`
(selfHostedCi false, exception inventory emptied) and remove the
`select-runner` job from all seven workflows: every lane now runs on
GitHub-hosted `ubuntu-24.04`, with each reviewed reusable receiving its
`runner` input explicitly. Net −181 lines.

## Why

The repository is public (confirmed intentional), and the runner-policy
engine fails closed on the visibility mismatch — currently red on main's
own CI and on #189. For public repositories the engine forbids
local-runner selector routing outright (`public-self-hosted-routing`),
so the reconciliation is the full hosted conversion, not a one-line
declaration. Hosted minutes are free for public repositories, and this
removes the standards CI load from the self-hosted fleet entirely.

Reusable pins move to `90f1c54` where a reviewed contract exists at that
revision; `standards-sync` stays at its newest registered contract
(`ec91c34`). This supersedes the selector-pin portion of #189.

## Verification

`node components/runner-policy/runner-policy.mjs --root .
--repository-visibility public` passes; 224/224 runner-policy component
tests pass; actionlint clean on all seven workflows; lefthook gauntlet
green.

## Related

No linked issue. #189 (superseded selector pins), #195 (policy
exception-category removal this builds on),
melodic-software/ci-workflows#141/#144 (the 90f1c54 revision).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01YZxBP1B8Hf7ZRaqDrP6ma9

Co-authored-by: Claude Fable 5 (1M context) <noreply@anthropic.com>
kyle-sexton added a commit to melodic-software/claude-code-plugins that referenced this pull request Jul 18, 2026
## What

Declare `visibility: "public"` in `.github/runner-policy.json`
(selfHostedCi false) and remove the `select-runner` job from all six
workflows: every lane runs on GitHub-hosted `ubuntu-24.04`, each
reviewed reusable receives its `runner` input explicitly, and pins move
to `90f1c54`. This branch also carries the standards-sync
materialization from #345 (the 90f1c54 contract registrations the policy
engine needs) — #345's diff becomes empty once this merges.

## Why

The repository is public but its runner policy declared private — the
engine fails closed on that mismatch, which is the `Runner policy` red
on #345 and every current PR. Same reconciliation as
melodic-software/standards#200: for public repositories the engine
forbids local-runner selector routing outright, so the fix is the full
hosted conversion. Hosted minutes are free for public repositories, and
this removes ccp's CI + claude-review load from the self-hosted fleet —
ccp was the largest queue consumer today (37 queued runs at peak).

## Verification

The synced policy engine passes with public visibility evidence
(`--repository-visibility public`); actionlint clean on all six
workflows. Required-check contexts are preserved (`ci-status`,
`do-not-merge / do-not-merge`, `pr-issue-linkage / pr-issue-linkage`,
`pr-title / pr-title`).

## Related

Closes #347. melodic-software/standards#200 (same posture conversion),
#345 (sync distribution folded in),
melodic-software/ci-workflows#141/#144 (the 90f1c54 revision).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01YZxBP1B8Hf7ZRaqDrP6ma9

---------

Co-authored-by: melodic-standards-sync[bot] <300666570+melodic-standards-sync[bot]@users.noreply.github.com>
Co-authored-by: Claude Fable 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant