mirror of
https://github.com/actions-runner-controller/actions-runner-controller.git
synced 2026-10-04 23:02:06 +02:00
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>