Clean up live ephemeral runners after terminal jobs - #4604
Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds a fallback cleanup path for live EphemeralRunner resources when GitHub reports a job is terminal but the runner process fails to exit, by persisting JobCompleted details onto the EphemeralRunner status and letting the controller delete the runner after a short grace period.
Changes:
- Add
status.jobCompletiontoEphemeralRunner(API types, deepcopy, CRDs/charts) and have the listener patch it onJobCompleted. - Update the
EphemeralRunnercontroller to wait a 30s grace period after a matching terminal event, then delete the runner via the existing finalizer path. - Add unit + envtest coverage for identity matching and the live-runner deletion behavior.
Reviewed changes
Copilot reviewed 9 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| controllers/actions.github.com/ephemeralrunner_job_completion_test.go | Unit test for jobCompletionMatchesRunner identity matching. |
| controllers/actions.github.com/ephemeralrunner_controller.go | Controller logic to requeue for grace period then delete runner after matching terminal job event. |
| controllers/actions.github.com/ephemeralrunner_controller_test.go | Envtest regression ensuring a live runner is deleted after terminal job event is recorded. |
| config/crd/bases/actions.github.com_ephemeralrunners.yaml | Adds status.jobCompletion schema to base CRD. |
| cmd/ghalistener/scaler/scaler.go | Records JobCompleted by patching status.jobCompletion on the exact EphemeralRunner. |
| cmd/ghalistener/scaler/scaler_test.go | New test verifying HandleJobCompleted performs the expected status patch request. |
| charts/gha-runner-scale-set-controller/crds/actions.github.com_ephemeralrunners.yaml | Propagates CRD schema update into Helm chart CRDs. |
| charts/gha-runner-scale-set-controller-experimental/crds/actions.github.com_ephemeralrunners.yaml | Propagates CRD schema update into experimental Helm chart CRDs. |
| apis/actions.github.com/v1alpha1/zz_generated.deepcopy.go | Generated deepcopy support for the new EphemeralRunnerJobCompletion type and pointer field. |
| apis/actions.github.com/v1alpha1/ephemeralrunner_types.go | Adds JobCompletion to status and defines EphemeralRunnerJobCompletion type. |
Files not reviewed (1)
- apis/actions.github.com/v1alpha1/zz_generated.deepcopy.go: Generated file
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Live validation against the latest stable release ( I backported this change to Evidence from the live run akua-dev/cnap#31789400724:
The normal Docker/Buildx/isolation canary 31788504613 also passed on the patched controller. Targeted backport tests pass. The release-tag full suite currently has an unrelated time-dependent failure because its checked-in test CA expired on 2026-07-13; the new focused controller regression and scaler tests pass. |
Closes #4603.
What this changes
ARC normally relies on the ephemeral runner process exiting after its one job. If that process remains alive after GitHub has already made the job terminal, the current
HandleJobCompleteddiscards the terminal event and ARC can retain the Pod, registration, and capacity slot indefinitely.This patch:
JobCompletedon the exactEphemeralRunnerstatus;NotFound, duplicate, and mismatched events as harmless;The normal path remains unchanged. In a controlled cancellation on the reported ARC/Kata-Firecracker setup, the runner exited and ARC cleaned everything within seven seconds, well before the fallback grace period.
TDD evidence
RED commit: robinbraemer@ec1febe
The regression test fails on the old implementation because
HandleJobCompletedacknowledges the event without making any Kubernetes request.GREEN commit: robinbraemer@86a5bed
Added coverage for:
Validation
go test ./cmd/ghalistener/scaler ./controllers/actions.github.com -count=1(full controller suite: 180s, pass)go vet ./cmd/ghalistener/scaler ./controllers/actions.github.com(pass)make generateandmake manifests(generated output included)git diff --check(pass)Runtime investigation
The original runner process hang remains unexplained because its Pod logs were gone after emergency cleanup. It was not a generally broken cancellation configuration: same ARC 0.14.2, official runner 2.336.0,
/home/runner/run.sh, Pod template, and Kata Firecracker RuntimeClass handled a controlled cancellation normally. The runner received cancellation, completed service cleanup, exited 0, and ARC deleted the Pod/ER/registration. This patch provides bounded ARC self-healing independently of the rare underlying runner/runtime trigger.