Files
actions-runner-controller/cmd
Nikola JokicandCopilot App 8148c24c37 Publish the desired runner count before the job event patches
Scale used to hold the replica patch back whenever the target dropped, so
that every job started patch had landed before the runner set controller
could act on a lower count. The reasoning was that
deleteIdleEphemeralRunners skips a runner only once it carries a job ID,
so a runner that had just picked up a job could otherwise be deleted.

That guard was unreachable. The controller only deletes idle runners
under Spec.PatchID == 0, and setDesiredWorkerState emits patch ID 0 only
when the target is unchanged and equal to MinRunners, or on the very
first patch, when no previous target exists. Neither can coincide with a
falling target, so a scale down never reaches the deletion path.
Exhaustively walking message sequences over every MinRunners/MaxRunners
pair finds no state where the two occur together.

So the replica patch has no reason to wait, and good reason to go first:
it is the only patch that creates runners, and therefore the one new jobs
wait on, while the job event patches are bookkeeping the controller reads
later. Sending it first also keeps it clear of the client rate limiter,
which a large batch of event patches would otherwise drain ahead of it.

The job events still have to land before Scale returns. The listener acks
the message the moment it does, and nothing other than these patches ever
writes Status.JobID, so a patch dropped after the ack would leave a busy
runner looking idle to the scale down that a later patch ID 0 permits.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
2026-09-18 15:05:54 +02:00
..