You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Replace the hand-rolled GitHub Actions scale set API calls with GitHub's actions/scaleset SDK, the standalone Go client extracted from the Actions Runner Controller.
Add pkg/github/scalesetclient, which implements the existing ActionsService interface on top of the SDK, with a compile-time assertion that the two stay in sync
Derive the GitHub API URL from GITHUB_URL and log a warning when the now-unused GITHUB_API_URL is set
Detect and skip messages that cannot be processed, rather than stalling the message loop
Raise the default MAX_RUNNERS to 9000
Testing
Run the integration against an organization and confirm the runner creates its scale set and begins processing messages.
Run a workflow and confirm a VM is provisioned and the job completes.
Two things on the new scalesetclient — both about how we read errors back from the SDK. Nice work overall, the GHES-shaped stub tests are great.
suggestion — pkg/github/scalesetclient/client.go, withStatusCode / statusCodePattern: We can drop the regex here and match on the SDK's typed error instead. actions/scaleset wraps every HTTP failure through wrapResponseErrorType, which attaches a sentinel (BadRequestError, UnauthorizedError, NotFoundError, ConflictError) using %w, and createMessageSession propagates it up the chain with %w as well. So the 409 we care about for session-conflict recovery is reachable directly:
iferrors.Is(err, scaleset.ConflictError) { ... }
Parsing status="(\d{3})" out of the formatted error string works today, but it silently breaks the moment upstream changes their error wording — and because the only consumer is createSessionWithRetry's "retry on 409, bail otherwise" logic, a silent break means startup stops recovering from a stale session (the common restart case). errors.Is on the exported sentinel gives the same signal and fails at compile time instead. I checked the SDK and it does not retry the 409 itself, so createSessionWithRetry should stay — it's just the status extraction that can go.
context / nit (keep it, but let's track it) — pkg/github/scalesetclient/client.go, GetMessage / skippableMessage: The strings.Contains(err.Error(), "unsupported message type") match is unfortunately the only handle the SDK gives us — parseRunnerScaleSetMessageResponse returns a plain fmt.Errorf("unsupported message type: %s", ...) with no sentinel and no wrap, so there's nothing typed to match on. Worth keeping the workaround: when the SDK hits an unknown outer message type it returns without acking the message, so the queue head never advances and the listener wedges permanently on it — the re-read-and-skip path is what prevents that. (Unknown inner job-message types are already safe; the SDK's inner switch has an empty default.) Two small asks: (a) a short comment here explaining why we're string-matching, so nobody "cleans it up" later, and (b) an upstream issue on actions/scaleset for a typed error (or SDK-side skip+ack of unknown outer types) — once that lands, this whole block can go.
Two things on the new scalesetclient — both about how we read errors back from the SDK. Nice work overall, the GHES-shaped stub tests are great.
suggestion — pkg/github/scalesetclient/client.go, withStatusCode / statusCodePattern: We can drop the regex here and match on the SDK's typed error instead. actions/scaleset wraps every HTTP failure through wrapResponseErrorType, which attaches a sentinel (BadRequestError, UnauthorizedError, NotFoundError, ConflictError) using %w, and createMessageSession propagates it up the chain with %w as well. So the 409 we care about for session-conflict recovery is reachable directly:
iferrors.Is(err, scaleset.ConflictError) { ... }
Parsing status="(\d{3})" out of the formatted error string works today, but it silently breaks the moment upstream changes their error wording — and because the only consumer is createSessionWithRetry's "retry on 409, bail otherwise" logic, a silent break means startup stops recovering from a stale session (the common restart case). errors.Is on the exported sentinel gives the same signal and fails at compile time instead. I checked the SDK and it does not retry the 409 itself, so createSessionWithRetry should stay — it's just the status extraction that can go.
context / nit (keep it, but let's track it) — pkg/github/scalesetclient/client.go, GetMessage / skippableMessage: The strings.Contains(err.Error(), "unsupported message type") match is unfortunately the only handle the SDK gives us — parseRunnerScaleSetMessageResponse returns a plain fmt.Errorf("unsupported message type: %s", ...) with no sentinel and no wrap, so there's nothing typed to match on. Worth keeping the workaround: when the SDK hits an unknown outer message type it returns without acking the message, so the queue head never advances and the listener wedges permanently on it — the re-read-and-skip path is what prevents that. (Unknown inner job-message types are already safe; the SDK's inner switch has an empty default.) Two small asks: (a) a short comment here explaining why we're string-matching, so nobody "cleans it up" later, and (b) an upstream issue on actions/scaleset for a typed error (or SDK-side skip+ack of unknown outer types) — once that lands, this whole block can go.
Good catches, but this doesn't work against the version we're pinned to. scaleset.ConflictError and wrapResponseErrorTypeactions/scaleset#86, merged 2026-06-10 into main. The latest published release is v0.4.0 from 2026-05-05, which predates it — that release exports only four sentinels (RunnerNotFoundError, RunnerExistsError, JobStillRunningError, MessageQueueTokenExpiredError) and carries the HTTP status only as text inside the formatted message, so there's nothing to errors.Is against for the 409 yet. This is the same for GetMessage/skippableMessage where v0.4.0 is a bare fmt.Errorf with no sentinel and no wrap.
Do you want to pin against main instead or keep as is and apply these improvements when a v0.5.0 lands upstream?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Replace the hand-rolled GitHub Actions scale set API calls with GitHub's
actions/scalesetSDK, the standalone Go client extracted from the Actions Runner Controller.pkg/github/scalesetclient, which implements the existingActionsServiceinterface on top of the SDK, with a compile-time assertion that the two stay in syncGITHUB_URLand log a warning when the now-unusedGITHUB_API_URLis setMAX_RUNNERSto 9000Testing