You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
0.14.2 (gha-runner-scale-set-controller); same code on master at 03328aa
Deployment Method
Helm
Checks
This isn't a question or user support case (For Q&A and community support, go to Discussions).
I've read the Changelog before submitting this issue and I'm sure it's not due to any recently-introduced backward-incompatible changes
To Reproduce
Install a gha-runner-scale-set release with minRunners: 0 and wait until the AutoscalingRunnerSet (ARS) carries the runner-scale-set-id annotation, i.e. the scale set is registered with the Actions service.
Remove the configured secret: kubectl delete secret <githubConfigSecret> -n <ns> (drop its autoscalingrunnerset.actions.github.com/cleanup-protection finalizer first if the chart created it).
kubectl delete autoscalingrunnerset <name> -n <ns> (or helm uninstall, or delete the namespace).
The ARS stays in Terminating indefinitely. The controller logs, on every requeue:
ERROR AutoscalingRunnerSet Failed to initialize Actions service client for updating a existing runner scale set {"error": "failed to resolve app config: failed to get kubernetes secret: \"<ns>/<githubConfigSecret>\""}
ERROR AutoscalingRunnerSet Failed to delete runner scale set
ERROR AutoscalingRunnerSet Failed to clean up resources during deletion
The finalizer autoscalingrunnerset.actions.github.com/finalizer is never removed, so kubectl delete / helm uninstall hang and a namespace containing the ARS can never finish terminating. Only manual intervention (kubectl patch ... --type=merge -p '{"metadata":{"finalizers":null}}') or recreating a secret with the old name unblocks it.
The same happens whenever the secret is gone for good rather than temporarily: the namespace is being torn down and the secret went first, the secret was rotated to a new name and the old one deleted before the ARS, or Helm removed the secret before the ARS during uninstall.
Describe the bug
During deletion, Reconcile calls cleanUpResources, which ends with deleteRunnerScaleSet:
deleteRunnerScaleSet builds an Actions client from githubConfigSecret via GetActionsService and returns the error if that fails:
log.Error(err, "Failed to clean up resources during deletion")
return ctrl.Result{}, err
A missing secret is a permanent condition, so the object is requeued forever with controller-runtime's exponential backoff. With many such objects (e.g. a namespace holding thousands of ARS objects being torn down) the workqueue fills with long-delayed items and every other reconcile in the controller slows down.
The deletion path already treats the two neighbouring "nothing to deregister" cases as terminal:
If the runner-scale-set-id annotation is absent, deleteRunnerScaleSet returns nil, documenting that "manual deletion of the scale set is required":
c.logger.Info("GitHub secret has already been deleted", "name", githubSecretName)
return
Only the "secret is NotFound" case blocks forever.
One detail for the fix: the resolver currently discards the underlying Kubernetes error. k8sResolver.appConfig formats it with %q of the key rather than wrapping the error (
returnnil, fmt.Errorf("failed to resolve app config: %v", err)
}
). So kerrors.IsNotFound(err) cannot currently see through the chain; both sites need %w (or a typed sentinel) before the controller can distinguish NotFound from transient failures.
Related but distinct: #4093 covers a secret that never existed (the ARS never registered, so the annotation-absent guard above already releases the ARS finalizer; the remaining wedge there is on EphemeralRunner, addressed by #4619). This report is about an ARS that did register and whose secret was removed afterwards, which neither of those covers.
Describe the expected behavior
When githubConfigSecret is definitively NotFound during deletion, the controller should:
log at Error level that the runner scale set (with its id) could not be deregistered from the Actions service and must be removed manually, matching the contract already documented for the missing-annotation case;
remove the runner-scale-set-id annotation;
return nil so the finalizer is released and the object, Helm release and namespace can finish deleting.
Transient errors (API server unavailable, 5xx from the Actions service, Forbidden on the secret while RBAC is being torn down) should keep returning the error and requeueing as today. Optionally emit a Kubernetes Event so the leaked scale set is visible.
Sketch of the change in deleteRunnerScaleSet:
actionsClient, err:=r.GetActionsService(ctx, autoscalingRunnerSet)
switch {
casekerrors.IsNotFound(err): // requires %w in secretresolverlogger.Error(err, "GitHub config secret no longer exists; runner scale set cannot be deregistered and must be deleted manually from the Actions service", "runnerScaleSetId", runnerScaleSetID)
// fall through to the annotation removal below and return nilcaseerr!=nil:
logger.Error(err, "Failed to initialize Actions service client for updating a existing runner scale set")
returnerrdefault:
// existing DeleteRunnerScaleSet call
}
Test: in autoscalingrunnerset_controller_test.go, create an ARS with the runner-scale-set-id annotation and a valid secret, delete the secret, delete the ARS, and Eventually assert the ARS is gone and the fake client's DeleteRunnerScaleSet was not called; a second case with a non-NotFound secret error asserts the ARS remains Terminating.
I am happy to open a PR with the envtest case if maintainers agree that releasing the finalizer is the right contract here.
Additional Context
# gha-runner-scale-set values (nothing unusual)githubConfigUrl: https://github.com/<org>githubConfigSecret: <name of a pre-created secret>minRunners: 0maxRunners: 5
Controller Logs
# repeated on every requeue, with growing backoff, until the finalizer is patched away by hand:
INFO AutoscalingRunnerSet Deleting resources
INFO AutoscalingRunnerSet Deleting the listener
INFO AutoscalingRunnerSet Listener is deleted
INFO AutoscalingRunnerSet deleting ephemeral runner sets
INFO AutoscalingRunnerSet Ephemeral runner set is deleted
INFO AutoscalingRunnerSet deleting runner scale set
INFO AutoscalingRunnerSet Deleting the runner scale set from Actions service
ERROR AutoscalingRunnerSet Failed to initialize Actions service client for updating a existing runner scale set {"error": "failed to resolve app config: failed to get kubernetes secret: \"<ns>/<githubConfigSecret>\""}
ERROR AutoscalingRunnerSet Failed to delete runner scale set
ERROR AutoscalingRunnerSet Failed to clean up resources during deletion
ERROR Reconciler error {"controller": "autoscalingrunnerset", ... , "error": "failed to resolve app config: failed to get kubernetes secret: \"<ns>/<githubConfigSecret>\""}
Runner Pod Logs
N/A - no runner pods exist at this point (minRunners: 0; EphemeralRunnerSet already deleted before the wedge).
Analysis prepared with an AI agent operated by KR-Ravindra, who verified the code paths.
Checks
Controller Version
0.14.2 (gha-runner-scale-set-controller); same code on
masterat 03328aaDeployment Method
Helm
Checks
To Reproduce
gha-runner-scale-setrelease withminRunners: 0and wait until theAutoscalingRunnerSet(ARS) carries therunner-scale-set-idannotation, i.e. the scale set is registered with the Actions service.kubectl delete secret <githubConfigSecret> -n <ns>(drop itsautoscalingrunnerset.actions.github.com/cleanup-protectionfinalizer first if the chart created it).kubectl delete autoscalingrunnerset <name> -n <ns>(orhelm uninstall, or delete the namespace).Terminatingindefinitely. The controller logs, on every requeue:The finalizer
autoscalingrunnerset.actions.github.com/finalizeris never removed, sokubectl delete/helm uninstallhang and a namespace containing the ARS can never finish terminating. Only manual intervention (kubectl patch ... --type=merge -p '{"metadata":{"finalizers":null}}') or recreating a secret with the old name unblocks it.The same happens whenever the secret is gone for good rather than temporarily: the namespace is being torn down and the secret went first, the secret was rotated to a new name and the old one deleted before the ARS, or Helm removed the secret before the ARS during uninstall.
Describe the bug
During deletion,
ReconcilecallscleanUpResources, which ends withdeleteRunnerScaleSet:deleteRunnerScaleSetbuilds an Actions client fromgithubConfigSecretviaGetActionsServiceand returns the error if that fails:actions-runner-controller/controllers/actions.github.com/autoscalingrunnerset_controller.go
Lines 768 to 772 in 03328aa
cleanUpResourcespropagates it:actions-runner-controller/controllers/actions.github.com/autoscalingrunnerset_controller.go
Lines 446 to 450 in 03328aa
Reconcilereturns the error without touching the finalizer:actions-runner-controller/controllers/actions.github.com/autoscalingrunnerset_controller.go
Lines 86 to 89 in 03328aa
A missing secret is a permanent condition, so the object is requeued forever with controller-runtime's exponential backoff. With many such objects (e.g. a namespace holding thousands of ARS objects being torn down) the workqueue fills with long-delayed items and every other reconcile in the controller slows down.
The deletion path already treats the two neighbouring "nothing to deregister" cases as terminal:
runner-scale-set-idannotation is absent,deleteRunnerScaleSetreturnsnil, documenting that "manual deletion of the scale set is required":actions-runner-controller/controllers/actions.github.com/autoscalingrunnerset_controller.go
Lines 742 to 757 in 03328aa
scaleset.NotFoundError), it logs and continues to release the finalizer (added in Re-register the runner scale set when it is gone from the Actions service #4571):actions-runner-controller/controllers/actions.github.com/autoscalingrunnerset_controller.go
Lines 775 to 779 in 03328aa
removeGitHubSecretFinalizertoleratesNotFound/Forbiddenon the very same secret ("GitHub secret has already been deleted"):actions-runner-controller/controllers/actions.github.com/autoscalingrunnerset_controller.go
Lines 1140 to 1142 in 03328aa
Only the "secret is NotFound" case blocks forever.
One detail for the fix: the resolver currently discards the underlying Kubernetes error.
k8sResolver.appConfigformats it with%qof the key rather than wrapping the error (actions-runner-controller/controllers/actions.github.com/secretresolver/secret_resolver.go
Lines 221 to 231 in 03328aa
GetActionsServicewraps with%v(actions-runner-controller/controllers/actions.github.com/secretresolver/secret_resolver.go
Lines 70 to 79 in 03328aa
kerrors.IsNotFound(err)cannot currently see through the chain; both sites need%w(or a typed sentinel) before the controller can distinguish NotFound from transient failures.Related but distinct: #4093 covers a secret that never existed (the ARS never registered, so the annotation-absent guard above already releases the ARS finalizer; the remaining wedge there is on
EphemeralRunner, addressed by #4619). This report is about an ARS that did register and whose secret was removed afterwards, which neither of those covers.Describe the expected behavior
When
githubConfigSecretis definitivelyNotFoundduring deletion, the controller should:runner-scale-set-idannotation;nilso the finalizer is released and the object, Helm release and namespace can finish deleting.Transient errors (API server unavailable, 5xx from the Actions service,
Forbiddenon the secret while RBAC is being torn down) should keep returning the error and requeueing as today. Optionally emit a Kubernetes Event so the leaked scale set is visible.Sketch of the change in
deleteRunnerScaleSet:Test: in
autoscalingrunnerset_controller_test.go, create an ARS with therunner-scale-set-idannotation and a valid secret, delete the secret, delete the ARS, andEventuallyassert the ARS is gone and the fake client'sDeleteRunnerScaleSetwas not called; a second case with a non-NotFound secret error asserts the ARS remainsTerminating.I am happy to open a PR with the envtest case if maintainers agree that releasing the finalizer is the right contract here.
Additional Context
Controller Logs
Runner Pod Logs
N/A - no runner pods exist at this point (minRunners: 0; EphemeralRunnerSet already deleted before the wedge).Analysis prepared with an AI agent operated by KR-Ravindra, who verified the code paths.