Skip to content

Retry transient Forbidden errors on ephemeral runner pod creation - #4719

Open
JoaoManierii wants to merge 8 commits into
actions:masterfrom
JoaoManierii:retry-transient-forbidden-pod-create
Open

JoaoManierii wants to merge 8 commits into
actions:masterfrom
JoaoManierii:retry-transient-forbidden-pod-create

Conversation

@JoaoManierii

@JoaoManierii JoaoManierii commented Oct 18, 2026 •

Copy link
Copy Markdown

This reopens #4711 by @fedor-rubalskiy with his commit unchanged. He closed it to follow the contributing guide and filed #4712 first. Neither has had a maintainer response yet, and we hit the same bug in production, so we would like to get the fix reviewed. @fedor-rubalskiy, if you would rather reopen yours, I'll close this one.

Fixes #4712
Fixes #4718

#4718 is the same failure seen from the scale set side: a transient 403 left a Failed runner that held its slot for hours. This change stops the transient 403 from producing that Failed runner. Points 2 and 3 in #4718 (a Failed runner holding a slot for other reasons, and surfacing it) can be followed up separately if you want them.

What it does

When the API server rejects a runner pod with a Forbidden whose message carries a gRPC status of ResourceExhausted, Unavailable or DeadlineExceeded, a backend of the API server failed. The pod itself was not rejected. On GKE this shows up as throttling with ENHANCE_YOUR_CALM. Those errors now get the handling that exceeded quota: already has since #4305:

  • runner younger than 10 minutes: requeue after 30s and try to create the pod again
  • runner older than that: delete the EphemeralRunner so the set re-creates it

Other Forbidden errors, such as a missing PriorityClass, an admission rejection or PermissionDenied, still mark the runner Failed with reason InvalidPod.

Why

Today any Forbidden other than quota marks the runner Failed on the first attempt. scaleTotal() keeps counting it, so the scale set loses a slot until someone deletes the runner by hand.

On controller 0.13.1 on GKE, four runners in four scale sets failed with ENHANCE_YOUR_CALM within one day, while the pod template was fine. On a set with maxRunners 4, the Failed runner held its slot for about 11.7 hours. Jobs waited with no runner for more than 2 hours in total. Logs and details are in #4718.

Testing

On this branch, Linux, go 1.27.1:

  • go test -run TestReconcileForbiddenPodCreation -v ./controllers/actions.github.com/: all 8 cases pass.
  • With the code list in isTransientForbidden emptied, the three transient cases fail and the other five pass.
  • go vet ./controllers/actions.github.com/ and gofmt -l controllers/actions.github.com/ are clean.
  • go test -short ./controllers/actions.github.com/... with envtest (Kubernetes 1.37) and Helm v4.2.2 passes, including the Ginkgo suite.

The testing notes in #4711 still apply: the test uses the fake client with an interceptor, because envtest cannot produce this error.

We can also run a controller build with this change in our cluster and report back.

🤖 Generated with Claude Code

When the API server fails a backend call while admitting a runner pod, it
answers 403 with the gRPC status in the message, for example
"pods ... is forbidden: rpc error: code = ResourceExhausted desc = stream
terminated by RST_STREAM with error code: ENHANCE_YOUR_CALM".

The IsForbidden branch only retried "exceeded quota:". Any other Forbidden
marked the EphemeralRunner Failed on the first attempt. A Failed runner is
counted by scaleTotal() and is not removed until the set is cleaned up, so
it keeps a replica slot and the queued job gets no runner.

Treat a Forbidden message carrying a ResourceExhausted, Unavailable or
DeadlineExceeded gRPC status like the quota case: requeue after 30s while
the runner is younger than 10 minutes, then delete it so it is re-created.
Other Forbidden errors still mark the runner Failed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI balanced review requested due to automatic review settings October 18, 2026 23:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@JoaoManierii
JoaoManierii requested a balanced review from Copilot October 24, 2026 04:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

2 participants