What happened
An AutoscalingRunnerSet loses its runner-scale-set-id annotation and then never re-registers. Every reconcile takes the teardown path and stops:
AutoscalingRunnerSet Ephemeral runner set is outdated {"autoscalingrunnerset":{"name":"lab-arc-runners"}}
AutoscalingRunnerSet Deleting the listener
AutoscalingRunnerSet Listener is deleted
AutoscalingRunnerSet deleting ephemeral runner sets
AutoscalingRunnerSet All ephemeral runner sets are deleted
AutoscalingRunnerSet deleting runner scale set
<no further lines for this object>
The object parks at status.phase: Outdated with no AutoscalingListener, no EphemeralRunnerSet, and no runner-scale-set-id annotation. It never reaches the create path. Because no runner matches the label, queued jobs queue indefinitely rather than fail, so nothing surfaces an error — our first occurrence ran ~8 hours before it was noticed, and it was noticed by accident.
One error does surface, intermittently — the controller adding a finalizer to an EphemeralRunnerSet that has already been deleted underneath it:
ERROR EphemeralRunnerSet Failed to update ephemeral runner set with finalizer added
{"ephemeralrunnerset":{"name":"lab-arc-runners-74bc6"},
"error":"ephemeralrunnersets.actions.github.com \"lab-arc-runners-74bc6\" not found"}
That looks like a create/delete race inside the reconcile: the ERS is created and then judged outdated and deleted within the same pass.
The reproducible pattern
A scale set name that has never been used registers on the first attempt and runs normally for 15–40 minutes. After its first teardown, that name never registers again.
| scale set name |
outcome |
lab-arc-runners |
wedged at its first teardown, never recovered |
lab-arc-ci (fresh) |
registered instantly (id 27), wedged ~25 min later, then refused |
lab-ci-pool (fresh) |
registered instantly (id 29), wedged ~15 min later |
lab-arc-runners-xl |
healthy all day — nothing ever tore it down |
Deleting the AutoscalingRunnerSet and letting it be recreated restores the pool for another window, which is the only workaround we have found.
Ruled out by direct test
- Chart and controller version — rolled both 0.14.2 → 0.14.1; behaviour byte-identical. 0.14.2 had run fine for 8 days beforehand.
- The scale set name — three different names, same behaviour.
- The runner template — the wedging pool hand-rolled a dind sidecar; converged it onto the healthy pool's
containerMode: dind shape; no change.
minRunners — 24, 8 and 0 all wedge. 0 survives longer but still wedges.
maxRunners — 2 and 30 both wedge.
- GitHub-side state — the ARC GitHub App reports zero live runner scale sets for the org via
_apis/runtime/runnerscalesets, so there is no name collision or orphan.
- Argo CD — this is deployed by Argo CD, so we tested it thoroughly:
ignoreDifferences on .metadata.annotations."runner-scale-set-id" plus RespectIgnoreDifferences=true is present and live.
- We then disabled automated sync entirely and synced exactly once by hand. It still wedged. Sync frequency is not the trigger.
resourceVersion on the ARS is stable between syncs; no write fight.
- Stale RBAC finalizers — cleared a leftover
actions.github.com/cleanup-protection finalizer on the *-gha-rs-no-permission ServiceAccount; no change.
- Networking — DNS and TLS from inside the controller namespace to the exact Actions tenant host succeed in ~0.1s.
Environment
- gha-runner-scale-set + gha-runner-scale-set-controller 0.14.2, also reproduced on 0.14.1
- k3s v1.32, single cluster, org-level registration via GitHub App
- Deployed by Argo CD;
minRunners: 0, maxRunners: 30, DinD via containerMode
What would help
Is there controller-side state tied to a scale set name that survives teardown and blocks re-registration? The fresh-name-works-once behaviour is the most specific clue we have, and it is what makes the failure permanent rather than transient.
What happened
An
AutoscalingRunnerSetloses itsrunner-scale-set-idannotation and then never re-registers. Every reconcile takes the teardown path and stops:The object parks at
status.phase: Outdatedwith noAutoscalingListener, noEphemeralRunnerSet, and norunner-scale-set-idannotation. It never reaches the create path. Because no runner matches the label, queued jobs queue indefinitely rather than fail, so nothing surfaces an error — our first occurrence ran ~8 hours before it was noticed, and it was noticed by accident.One error does surface, intermittently — the controller adding a finalizer to an
EphemeralRunnerSetthat has already been deleted underneath it:That looks like a create/delete race inside the reconcile: the ERS is created and then judged outdated and deleted within the same pass.
The reproducible pattern
A scale set name that has never been used registers on the first attempt and runs normally for 15–40 minutes. After its first teardown, that name never registers again.
lab-arc-runnerslab-arc-ci(fresh)lab-ci-pool(fresh)lab-arc-runners-xlDeleting the
AutoscalingRunnerSetand letting it be recreated restores the pool for another window, which is the only workaround we have found.Ruled out by direct test
containerMode: dindshape; no change.minRunners— 24, 8 and 0 all wedge. 0 survives longer but still wedges.maxRunners— 2 and 30 both wedge._apis/runtime/runnerscalesets, so there is no name collision or orphan.ignoreDifferenceson.metadata.annotations."runner-scale-set-id"plusRespectIgnoreDifferences=trueis present and live.resourceVersionon the ARS is stable between syncs; no write fight.actions.github.com/cleanup-protectionfinalizer on the*-gha-rs-no-permissionServiceAccount; no change.Environment
minRunners: 0,maxRunners: 30, DinD viacontainerModeWhat would help
Is there controller-side state tied to a scale set name that survives teardown and blocks re-registration? The fresh-name-works-once behaviour is the most specific clue we have, and it is what makes the failure permanent rather than transient.