Skip to content

feat(arc): repository-scoped runner scale sets - #142

Merged
Mearman merged 8 commits into
mainfrom
feat/repository-scoped-scale-sets
Oct 25, 2026
Merged

Mearman merged 8 commits into
mainfrom
feat/repository-scoped-scale-sets

Conversation

@Mearman

@Mearman Mearman commented Oct 25, 2026

Copy link
Copy Markdown
Member

A github_runner_arc_orgs entry can now set repository, which registers its scale sets in that one repository: githubConfigUrl becomes https://github.com/<name>/<repository>, and name may be a user or an organisation. That is the only way a repository of a personal account can use the fleet, since a personal account has no organisation runners. Without repository an entry behaves exactly as before.

An entry is now identified by its scope (name, or name/repository, from the new arc_scope filter). Everything that keyed on the login keys on the scope instead: the duplicate, release-name and cross-cluster checks in arc_profiles and arc_cluster_profiles, resolved installation ids, App-minted pull tokens, the selection of an entry's profiles in install_org.yml and main.yml, and the app_id check against an existing App Secret (which previously took the first entry with a matching name). So an organisation's entry and any number of its repositories' entries can sit side by side.

Derived names for a repository scope join the login and the repository with -- (lower-cased, with . and _ turned into -): example-user / example-repo gives release example-user--example-repo-runners, namespace arc-runners-example-user--example-repo, App Secret example-user--example-repo-github-app and default runs-on label example-user--example-repo-runners. GitHub logins cannot contain --, so these never meet an organisation entry's names, and anything that still collides (two repository names differing only in . and _) is refused by the existing namespace and release checks.

A repository scope resolves its installation through GET /repos/{owner}/{repo}/installation rather than matching account.login in GET /app/installations, so an App installed on the account but not on that repository fails at install time. The organisation path is unchanged; account there is a user or an organisation, so the login match would also find a user's installation, but not prove it reaches the repository.

The manifest flow gains github_runner_arc_app_setup_repository and github_runner_arc_app_setup_owner_type (organization or user). A repository App asks for repository Administration write and Metadata read; a user App posts to https://github.com/settings/apps/new instead of the organisation's App settings and refuses to run without a repository. github_runner_arc_app_setup_permissions now defaults to empty, meaning the scope's documented permissions (unchanged for an organisation). bootstrap.sh takes RUNNER_REPOSITORY.

Two fixes this needed:

  • Derived release names are now checked against Helm's 53-character limit (maxReleaseNameLen in Helm's pkg/chart/v2/util/validate_name.go) rather than the 63-character DNS label limit, since a repository name makes a too-long release far more likely and Helm would only refuse it at helm upgrade.
  • The 1Password adapter read, cached and filled App keys by entry name, so two entries of one login naming different keys got the same one. It now keys them by private_key_op_reference. An existing cache keyed by name is simply re-read for the missing references.

Unchanged and documented as scope-independent: the runner reaper (it reads runs from /repos/{owner}/{repo}/actions/runs/... for the repository ARC recorded on the runner), the autoscaler and its pooled budget and the heartbeat (both work on namespace/release targets), and the pull Secret renewal (it uses whatever App Secret the profile's namespace holds). With github_runner_arc_manage_secrets: false a repository entry reads its App's id from the existing App Secret under its app_secret_name or derived name, like any other.

Sources:

Not verified here, and said so in the role README where it matters: ARC's own instructions describe an organisation-owned App, and I have not run a repository scale set against a personal account's own App; nor whether an installation token of an App installed on a user reads that user's private GHCR packages (github_runner_arc_image_pull_secret_source: app), so the README points a personal account at static or none until a run proves it.

Each new test was checked by breaking the code it covers (scope keying in every check, the separator, the URL, validation, the Helm limit, the 1Password keying, the App Secret app_id lookup, the rendered githubConfigUrl, and the manifest form's action, permissions, secret targets, user-without-repository check and phase 2 command) and watching it fail.

An entry with repository registers its scale sets in name/repository,
where name may be a user or an organisation, so a repository of a
personal account can use the fleet. Each profile now records its scope,
repository and githubConfigUrl.

Names derived for a repository scope join the login and the repository
with a double hyphen, which no GitHub login contains, so they never
collide with the organisation's own or another repository's. The
duplicate, release and cross-cluster checks key on the scope instead of
the login, and arc_scope gives the tasks the same key.
Helm refuses a release name over 53 characters, so a name between that
and the DNS label limit passed validation and failed only at helm
upgrade. Release names are now checked against Helm's own limit.
The adapter read one key per entry name, so two github_runner_arc_orgs
entries of one login naming different keys (an organisation and one of
its repositories) got the same key. Keys are now read, cached and filled
by their 1Password reference.
A profile registers with its scope's githubConfigUrl, so a repository
entry's scale sets register in that repository. Installation ids, pull
tokens, an entry's profiles and an existing App Secret's app_id check
are looked up by scope rather than by login, so an organisation entry
and its repository entries never stand in for one another.

A repository scope resolves its installation through
GET /repos/{owner}/{repo}/installation, which answers only for an
installation that can reach the repository.
github_runner_arc_app_setup_repository makes the manifest flow create an
App for a repository entry: it asks for the repository administration
write and metadata read permissions ARC documents for that scope, waits
for an installation that reaches the repository, and writes the App
Secret into that entry's namespaces under its derived name.

github_runner_arc_app_setup_owner_type user posts the manifest to a
personal account's App settings, and fails without a repository, since a
personal account has no organisation runners. An empty
github_runner_arc_app_setup_permissions now means the scope's documented
permissions, and arc_default_app_secret gives the setup and the profile
expansion one derivation of the App Secret's name.
RUNNER_REPOSITORY sets the entry's repository, so a single machine can
bring up runners for one repository of a user or an organisation.
Covers the repository key and its githubConfigUrl, the names it derives
and why they never collide, the App permissions for each scope with
their sources, and that every repository needs an entry of its own.
@Mearman
Mearman marked this pull request as ready for review October 25, 2026 08:10
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
🔒 Security Review ⚠️ Failed 2026-10-25T08:10:16.465031Z
We couldn’t complete this review. Comment @codex security review to try again.
45c90de Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

This security review couldn’t start because the paying account has exhausted its Codex allowance and has insufficient available credits. The payer can check their balance and add credits in the Codex usage dashboard, then try again. If you do not manage the paying account, contact this repository's admins.

@Mearman
Mearman merged commit 389b727 into main Oct 25, 2026
50 of 51 checks passed
@Mearman
Mearman deleted the feat/repository-scoped-scale-sets branch October 25, 2026 08:10
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 2.9.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant