Skip to content

feat(arc): add an optional reaper for runner pods that outlive their job - #68

Merged
Mearman merged 8 commits into
mainfrom
feat/stuck-runner-reaper
Oct 8, 2026
Merged

Mearman merged 8 commits into
mainfrom
feat/stuck-runner-reaper

Conversation

@Mearman

@Mearman Mearman commented Oct 8, 2026

Copy link
Copy Markdown
Member

Adds the optional runner reaper from the issue to the github_runner_arc role, plus the activeDeadlineSeconds backstop.

With github_runner_arc_runner_reaper_enabled on, each runner namespace gets a CronJob that runs files/reap-runners.sh from a ConfigMap, in the pull Secret renewal's image (it already has bash, curl, jq, kubectl and openssl). Each pass mints an installation token from the App Secret restricted to actions: read, lists the namespace's EphemeralRunners, and for each runner ARC has recorded a workflow run on, reads that run. It deletes the runner when the run is completed (cancelled included), when the runner's own job in a still-running run is completed, or when that job has been running longer than github_runner_arc_runner_reaper_max_job_seconds. Completed runs and jobs only count once they have been completed for a grace period, so a runner exiting normally is left to ARC. Every deletion is logged with the run, the repository and the reason.

A runner with no recorded run is idle and is never looked up or deleted, and nor is one already being deleted. A runner is only deleted on a positive answer from GitHub: if the token cannot be minted or the runners cannot be listed, the pass deletes nothing and fails; if one runner's run or jobs cannot be read, that runner is left alone, the others are still judged, and the pass fails at the end. The Role allows only list and delete on ephemeralrunners; the App Secret reaches the pod as a volume.

The App needs the Actions repository permission (read) on the repositories whose jobs the runners take. The role's default App manifest only asks for organisation self-hosted runners (write), so an existing App needs that permission added and approved on its installation; until then each pass fails with a message naming it and deletes nothing.

The JWT signing and token minting moved out of the renewal script into files/github-app-token.sh, which the renewer image and the reaper's ConfigMap both carry.

One thing worth knowing: ARC 0.15 keeps the pod of a runner GitHub still reports as running a job, even after the EphemeralRunner is deleted, until GitHub releases the job. So for a job past the bound, the deletion takes effect once GitHub ends the job, while for a completed or cancelled run it takes effect straight away. github_runner_arc_runner_active_deadline_seconds is the backstop that stops the pod regardless.

Closes #67

Move the JWT signing and token minting out of the pull Secret renewal
into a sourced file in the role's files, so another in-cluster job can
mint a token restricted to its own permissions the same way.
The renewer image carries it beside the renewal script.
One pass lists a namespace's EphemeralRunners and, for each one ARC has
assigned a job, reads the job's run from GitHub with an actions: read
installation token. It deletes the runner when the run or its own job
is completed, or when the job has run past a configured bound, and
never touches an idle runner or one it could not judge.
…amespace

Off by default. When on, each scale-set profile's namespace gets a
CronJob that runs the reaper from a ConfigMap in the pull Secret
renewal's image, with a Role that may only list and delete
EphemeralRunners there and the App Secret mounted as a volume.
Turning it off removes all of it.
github_runner_arc_runner_active_deadline_seconds puts the deadline on
every runner pod as a backstop that needs no GitHub call. Unset leaves
the runner pod spec as it was.
Applies ARC's CRDs and the role's reaper manifests to a kind cluster,
records jobs on EphemeralRunners as ARC's listener does, and runs a
pass against a stand-in for GitHub: the runner past the bound and the
cancelled run's runner go, the idle and busy runners stay, the
ServiceAccount can do nothing else, and a refused token deletes
nothing.
@Mearman
Mearman marked this pull request as ready for review October 8, 2026 13:43
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 8, 2026 •

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 ✅ Completed 2026-10-08T13:46:52.563921Z 782611a 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.

@Mearman
Mearman merged commit c9d7f3b into main Oct 8, 2026
16 checks passed
@Mearman
Mearman deleted the feat/stuck-runner-reaper branch October 8, 2026 13:47
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

🎉 This PR is included in version 1.26.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.

Add an optional reaper for runner pods that outlive their job

1 participant