From 18f2450d7107a9ec58033b8ed4de708f5b700037 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 12:31:58 +0000 Subject: [PATCH 1/8] chore: update Node versions (#4075) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/Misc/externals.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Misc/externals.sh b/src/Misc/externals.sh index 9a0c55cad..409bb7558 100755 --- a/src/Misc/externals.sh +++ b/src/Misc/externals.sh @@ -7,7 +7,7 @@ NODE_ALPINE_URL=https://github.com/actions/alpine_nodejs/releases/download # When you update Node versions you must also create a new release of alpine_nodejs at that updated version. # Follow the instructions here: https://github.com/actions/alpine_nodejs?tab=readme-ov-file#getting-started NODE20_VERSION="20.19.5" -NODE24_VERSION="24.9.0" +NODE24_VERSION="24.10.0" get_abs_path() { # exploits the fact that pwd will print abs path when no args From a12731d34de0fdf71dc24748478519ba743a0a83 Mon Sep 17 00:00:00 2001 From: Nikola Jokic Date: Mon, 13 Oct 2025 15:40:16 +0200 Subject: [PATCH 2/8] Include k8s novolume (version v0.8.0) (#4063) --- images/Dockerfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/images/Dockerfile b/images/Dockerfile index 4ff7933f1..cefe05a74 100644 --- a/images/Dockerfile +++ b/images/Dockerfile @@ -21,6 +21,10 @@ RUN curl -f -L -o runner-container-hooks.zip https://github.com/actions/runner-c && unzip ./runner-container-hooks.zip -d ./k8s \ && rm runner-container-hooks.zip +RUN curl -f -L -o runner-container-hooks.zip https://github.com/actions/runner-container-hooks/releases/download/v0.8.0/actions-runner-hooks-k8s-0.8.0.zip \ + && unzip ./runner-container-hooks.zip -d ./k8s-novolume \ + && rm runner-container-hooks.zip + RUN export RUNNER_ARCH=${TARGETARCH} \ && if [ "$RUNNER_ARCH" = "amd64" ]; then export DOCKER_ARCH=x86_64 ; fi \ && if [ "$RUNNER_ARCH" = "arm64" ]; then export DOCKER_ARCH=aarch64 ; fi \ From afe4fc8446c5045541e9b9d0572f10bb3f57c9cc Mon Sep 17 00:00:00 2001 From: Tingluo Huang Date: Mon, 13 Oct 2025 12:22:10 -0400 Subject: [PATCH 3/8] Make sure runner-admin has both auth_url and auth_url_v2. (#4066) --- .../Configuration/ConfigurationManager.cs | 15 +++++++++++++++ src/Sdk/DTWebApi/WebApi/Runner.cs | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/Runner.Listener/Configuration/ConfigurationManager.cs b/src/Runner.Listener/Configuration/ConfigurationManager.cs index bb5624e34..2c18d8d60 100644 --- a/src/Runner.Listener/Configuration/ConfigurationManager.cs +++ b/src/Runner.Listener/Configuration/ConfigurationManager.cs @@ -284,6 +284,7 @@ namespace GitHub.Runner.Listener.Configuration { var runner = await _dotcomServer.ReplaceRunnerAsync(runnerSettings.PoolId, agent, runnerSettings.GitHubUrl, registerToken, publicKeyXML); runnerSettings.ServerUrlV2 = runner.RunnerAuthorization.ServerUrl; + runnerSettings.UseV2Flow = true; // if we are using runner admin, we also need to hit broker agent.Id = runner.Id; agent.Authorization = new TaskAgentAuthorization() @@ -291,6 +292,13 @@ namespace GitHub.Runner.Listener.Configuration AuthorizationUrl = runner.RunnerAuthorization.AuthorizationUrl, ClientId = new Guid(runner.RunnerAuthorization.ClientId) }; + + if (!string.IsNullOrEmpty(runner.RunnerAuthorization.LegacyAuthorizationUrl?.AbsoluteUri)) + { + agent.Authorization.AuthorizationUrl = runner.RunnerAuthorization.LegacyAuthorizationUrl; + agent.Properties["EnableAuthMigrationByDefault"] = true; + agent.Properties["AuthorizationUrlV2"] = runner.RunnerAuthorization.AuthorizationUrl.AbsoluteUri; + } } else { @@ -342,6 +350,13 @@ namespace GitHub.Runner.Listener.Configuration AuthorizationUrl = runner.RunnerAuthorization.AuthorizationUrl, ClientId = new Guid(runner.RunnerAuthorization.ClientId) }; + + if (!string.IsNullOrEmpty(runner.RunnerAuthorization.LegacyAuthorizationUrl?.AbsoluteUri)) + { + agent.Authorization.AuthorizationUrl = runner.RunnerAuthorization.LegacyAuthorizationUrl; + agent.Properties["EnableAuthMigrationByDefault"] = true; + agent.Properties["AuthorizationUrlV2"] = runner.RunnerAuthorization.AuthorizationUrl.AbsoluteUri; + } } else { diff --git a/src/Sdk/DTWebApi/WebApi/Runner.cs b/src/Sdk/DTWebApi/WebApi/Runner.cs index f3fdbf60e..26c3e2291 100644 --- a/src/Sdk/DTWebApi/WebApi/Runner.cs +++ b/src/Sdk/DTWebApi/WebApi/Runner.cs @@ -18,6 +18,16 @@ namespace GitHub.DistributedTask.WebApi internal set; } + /// + /// The url to refresh tokens with legacy service + /// + [JsonProperty("legacy_authorization_url")] + public Uri LegacyAuthorizationUrl + { + get; + internal set; + } + /// /// The url to connect to poll for messages /// From 1eb15f28a79df30b0786d0e9e57d80c3749b5d81 Mon Sep 17 00:00:00 2001 From: Tingluo Huang Date: Mon, 13 Oct 2025 16:21:32 -0400 Subject: [PATCH 4/8] Report job has infra failure to run-service (#4073) --- src/Runner.Common/RunServer.cs | 4 +++- src/Runner.Listener/JobDispatcher.cs | 2 +- src/Runner.Worker/ActionManager.cs | 10 +++++----- src/Runner.Worker/ExecutionContext.cs | 8 ++++++-- src/Runner.Worker/GlobalContext.cs | 1 + src/Runner.Worker/JobRunner.cs | 2 +- src/Sdk/RSWebApi/Contracts/CompleteJobRequest.cs | 3 +++ src/Sdk/RSWebApi/Contracts/IssueExtensions.cs | 1 + src/Sdk/RSWebApi/RunServiceHttpClient.cs | 2 ++ 9 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/Runner.Common/RunServer.cs b/src/Runner.Common/RunServer.cs index b57d2754b..cfdb98cb9 100644 --- a/src/Runner.Common/RunServer.cs +++ b/src/Runner.Common/RunServer.cs @@ -30,6 +30,7 @@ namespace GitHub.Runner.Common string environmentUrl, IList telemetry, string billingOwnerId, + string infrastructureFailureCategory, CancellationToken token); Task RenewJobAsync(Guid planId, Guid jobId, CancellationToken token); @@ -80,11 +81,12 @@ namespace GitHub.Runner.Common string environmentUrl, IList telemetry, string billingOwnerId, + string infrastructureFailureCategory, CancellationToken cancellationToken) { CheckConnection(); return RetryRequest( - async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, jobAnnotations, environmentUrl, telemetry, billingOwnerId, cancellationToken), cancellationToken, + async () => await _runServiceHttpClient.CompleteJobAsync(requestUri, planId, jobId, result, outputs, stepResults, jobAnnotations, environmentUrl, telemetry, billingOwnerId, infrastructureFailureCategory, cancellationToken), cancellationToken, shouldRetry: ex => ex is not VssUnauthorizedException && // HTTP status 401 ex is not TaskOrchestrationJobNotFoundException); // HTTP status 404 diff --git a/src/Runner.Listener/JobDispatcher.cs b/src/Runner.Listener/JobDispatcher.cs index f98204b42..bbc09593c 100644 --- a/src/Runner.Listener/JobDispatcher.cs +++ b/src/Runner.Listener/JobDispatcher.cs @@ -1211,7 +1211,7 @@ namespace GitHub.Runner.Listener jobAnnotations.Add(annotation.Value); } - await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, jobAnnotations: jobAnnotations, environmentUrl: null, telemetry: null, billingOwnerId: message.BillingOwnerId, CancellationToken.None); + await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, TaskResult.Failed, outputs: null, stepResults: null, jobAnnotations: jobAnnotations, environmentUrl: null, telemetry: null, billingOwnerId: message.BillingOwnerId, infrastructureFailureCategory: null, CancellationToken.None); } catch (Exception ex) { diff --git a/src/Runner.Worker/ActionManager.cs b/src/Runner.Worker/ActionManager.cs index 9a21aeb4c..c2af24bbc 100644 --- a/src/Runner.Worker/ActionManager.cs +++ b/src/Runner.Worker/ActionManager.cs @@ -111,7 +111,7 @@ namespace GitHub.Runner.Worker { // Log the error and fail the PrepareActionsAsync Initialization. Trace.Error($"Caught exception from PrepareActionsAsync Initialization: {ex}"); - executionContext.InfrastructureError(ex.Message); + executionContext.InfrastructureError(ex.Message, category: "resolve_action"); executionContext.Result = TaskResult.Failed; throw; } @@ -119,7 +119,7 @@ namespace GitHub.Runner.Worker { // Log the error and fail the PrepareActionsAsync Initialization. Trace.Error($"Caught exception from PrepareActionsAsync Initialization: {ex}"); - executionContext.InfrastructureError(ex.Message); + executionContext.InfrastructureError(ex.Message, category: "invalid_action_download"); executionContext.Result = TaskResult.Failed; throw; } @@ -777,15 +777,15 @@ namespace GitHub.Runner.Worker IOUtil.DeleteDirectory(destDirectory, executionContext.CancellationToken); Directory.CreateDirectory(destDirectory); - if (downloadInfo.PackageDetails != null) + if (downloadInfo.PackageDetails != null) { executionContext.Output($"##[group]Download immutable action package '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}'"); executionContext.Output($"Version: {downloadInfo.PackageDetails.Version}"); executionContext.Output($"Digest: {downloadInfo.PackageDetails.ManifestDigest}"); executionContext.Output($"Source commit SHA: {downloadInfo.ResolvedSha}"); executionContext.Output("##[endgroup]"); - } - else + } + else { executionContext.Output($"Download action repository '{downloadInfo.NameWithOwner}@{downloadInfo.Ref}' (SHA:{downloadInfo.ResolvedSha})"); } diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index e64c6e24a..3410d1831 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -522,6 +522,10 @@ namespace GitHub.Runner.Worker if (annotation != null) { stepResult.Annotations.Add(annotation.Value); + if (annotation.Value.IsInfrastructureIssue && string.IsNullOrEmpty(Global.InfrastructureFailureCategory)) + { + Global.InfrastructureFailureCategory = issue.Category; + } } }); @@ -1335,9 +1339,9 @@ namespace GitHub.Runner.Worker } // Do not add a format string overload. See comment on ExecutionContext.Write(). - public static void InfrastructureError(this IExecutionContext context, string message) + public static void InfrastructureError(this IExecutionContext context, string message, string category = null) { - var issue = new Issue() { Type = IssueType.Error, Message = message, IsInfrastructureIssue = true }; + var issue = new Issue() { Type = IssueType.Error, Message = message, IsInfrastructureIssue = true, Category = category }; context.AddIssue(issue, ExecutionContextLogOptions.Default); } diff --git a/src/Runner.Worker/GlobalContext.cs b/src/Runner.Worker/GlobalContext.cs index 32b58384f..5a4c7babd 100644 --- a/src/Runner.Worker/GlobalContext.cs +++ b/src/Runner.Worker/GlobalContext.cs @@ -27,6 +27,7 @@ namespace GitHub.Runner.Worker public StepsContext StepsContext { get; set; } public Variables Variables { get; set; } public bool WriteDebug { get; set; } + public string InfrastructureFailureCategory { get; set; } public JObject ContainerHookState { get; set; } } } diff --git a/src/Runner.Worker/JobRunner.cs b/src/Runner.Worker/JobRunner.cs index 1390af13b..72ee5a403 100644 --- a/src/Runner.Worker/JobRunner.cs +++ b/src/Runner.Worker/JobRunner.cs @@ -321,7 +321,7 @@ namespace GitHub.Runner.Worker { try { - await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, environmentUrl, telemetry, billingOwnerId: message.BillingOwnerId, default); + await runServer.CompleteJobAsync(message.Plan.PlanId, message.JobId, result, jobContext.JobOutputs, jobContext.Global.StepsResult, jobContext.Global.JobAnnotations, environmentUrl, telemetry, billingOwnerId: message.BillingOwnerId, infrastructureFailureCategory: jobContext.Global.InfrastructureFailureCategory, default); return result; } catch (VssUnauthorizedException ex) diff --git a/src/Sdk/RSWebApi/Contracts/CompleteJobRequest.cs b/src/Sdk/RSWebApi/Contracts/CompleteJobRequest.cs index a9ba71a57..c3049dc10 100644 --- a/src/Sdk/RSWebApi/Contracts/CompleteJobRequest.cs +++ b/src/Sdk/RSWebApi/Contracts/CompleteJobRequest.cs @@ -35,5 +35,8 @@ namespace GitHub.Actions.RunService.WebApi [DataMember(Name = "billingOwnerId", EmitDefaultValue = false)] public string BillingOwnerId { get; set; } + + [DataMember(Name = "infrastructureFailureCategory", EmitDefaultValue = false)] + public string InfrastructureFailureCategory { get; set; } } } diff --git a/src/Sdk/RSWebApi/Contracts/IssueExtensions.cs b/src/Sdk/RSWebApi/Contracts/IssueExtensions.cs index 113eaa7e0..34aaff813 100644 --- a/src/Sdk/RSWebApi/Contracts/IssueExtensions.cs +++ b/src/Sdk/RSWebApi/Contracts/IssueExtensions.cs @@ -42,6 +42,7 @@ namespace Sdk.RSWebApi.Contracts StartColumn = columnNumber, EndColumn = endColumnNumber, StepNumber = stepNumber, + IsInfrastructureIssue = issue.IsInfrastructureIssue ?? false }; } diff --git a/src/Sdk/RSWebApi/RunServiceHttpClient.cs b/src/Sdk/RSWebApi/RunServiceHttpClient.cs index bb1407706..83afdafff 100644 --- a/src/Sdk/RSWebApi/RunServiceHttpClient.cs +++ b/src/Sdk/RSWebApi/RunServiceHttpClient.cs @@ -131,6 +131,7 @@ namespace GitHub.Actions.RunService.WebApi string environmentUrl, IList telemetry, string billingOwnerId, + string infrastructureFailureCategory, CancellationToken cancellationToken = default) { HttpMethod httpMethod = new HttpMethod("POST"); @@ -145,6 +146,7 @@ namespace GitHub.Actions.RunService.WebApi EnvironmentUrl = environmentUrl, Telemetry = telemetry, BillingOwnerId = billingOwnerId, + InfrastructureFailureCategory = infrastructureFailureCategory }; requestUri = new Uri(requestUri, "completejob"); From f74be39e77f2a23df5c854dc19394008a7838b1a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 10:13:15 -0400 Subject: [PATCH 5/8] Bump actions/setup-node from 5 to 6 (#4078) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/dependency-check.yml | 2 +- .github/workflows/npm-audit-typescript.yml | 2 +- .github/workflows/npm-audit.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/dependency-check.yml b/.github/workflows/dependency-check.yml index 5c8332d3c..c8b9a56c0 100644 --- a/.github/workflows/dependency-check.yml +++ b/.github/workflows/dependency-check.yml @@ -31,7 +31,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: "20" diff --git a/.github/workflows/npm-audit-typescript.yml b/.github/workflows/npm-audit-typescript.yml index 515daebd3..6d9d89758 100644 --- a/.github/workflows/npm-audit-typescript.yml +++ b/.github/workflows/npm-audit-typescript.yml @@ -9,7 +9,7 @@ jobs: steps: - uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: "20" - name: NPM install and audit fix with TypeScript auto-repair diff --git a/.github/workflows/npm-audit.yml b/.github/workflows/npm-audit.yml index 92318afed..83321f4be 100644 --- a/.github/workflows/npm-audit.yml +++ b/.github/workflows/npm-audit.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v5 - name: Setup Node.js - uses: actions/setup-node@v5 + uses: actions/setup-node@v6 with: node-version: "20" From ff775ca101b17de686168ce0092bd281f7b59c7b Mon Sep 17 00:00:00 2001 From: Tingluo Huang Date: Tue, 14 Oct 2025 10:31:32 -0400 Subject: [PATCH 6/8] Prepare runner release v2.329.0 (#4079) --- releaseNote.md | 49 ++++++++++++++++++++++++++++++++++------------- src/runnerversion | 2 +- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/releaseNote.md b/releaseNote.md index 91dfdd1ae..9c749ad34 100644 --- a/releaseNote.md +++ b/releaseNote.md @@ -1,20 +1,43 @@ ## What's Changed -* Update Docker to v28.3.2 and Buildx to v0.26.1 by @github-actions[bot] in https://github.com/actions/runner/pull/3953 -* Fix if statement structure in update script and variable reference by @salmanmkc in https://github.com/actions/runner/pull/3956 -* Add V2 flow for runner deletion by @Samirat in https://github.com/actions/runner/pull/3954 -* Node 20 -> Node 24 migration feature flagging, opt-in and opt-out environment variables by @salmanmkc in https://github.com/actions/runner/pull/3948 -* Update Node20 and Node24 to latest by @djs-intel in https://github.com/actions/runner/pull/3972 -* Redirect supported OS doc section to current public Docs location by @corycalahan in https://github.com/actions/runner/pull/3979 -* Bump Microsoft.NET.Test.Sdk from 17.13.0 to 17.14.1 by @dependabot[bot] in https://github.com/actions/runner/pull/3975 -* Bump Azure.Storage.Blobs from 12.24.0 to 12.25.0 by @dependabot[bot] in https://github.com/actions/runner/pull/3974 -* Bump actions/download-artifact from 4 to 5 by @dependabot[bot] in https://github.com/actions/runner/pull/3973 -* Bump actions/checkout from 4 to 5 by @dependabot[bot] in https://github.com/actions/runner/pull/3982 +* Update safe_sleep.sh for bug when scheduler is paused for more than 1 second by @horner in https://github.com/actions/runner/pull/3157 +* Acknowledge runner request by @ericsciple in https://github.com/actions/runner/pull/3996 +* Update Docker to v28.3.3 and Buildx to v0.27.0 by @github-actions[bot] in https://github.com/actions/runner/pull/3999 +* Update dotnet sdk to latest version @8.0.413 by @github-actions[bot] in https://github.com/actions/runner/pull/4000 +* Bump actions/attest-build-provenance from 2 to 3 by @dependabot[bot] in https://github.com/actions/runner/pull/4002 +* Bump @typescript-eslint/eslint-plugin from 6.7.2 to 8.35.0 in /src/Misc/expressionFunc/hashFiles by @dependabot[bot] in https://github.com/actions/runner/pull/3920 +* Bump husky from 8.0.3 to 9.1.7 in /src/Misc/expressionFunc/hashFiles by @dependabot[bot] in https://github.com/actions/runner/pull/3842 +* Bump @vercel/ncc from 0.38.0 to 0.38.3 in /src/Misc/expressionFunc/hashFiles by @dependabot[bot] in https://github.com/actions/runner/pull/3841 +* Bump eslint-plugin-github from 4.10.0 to 4.10.2 in /src/Misc/expressionFunc/hashFiles by @dependabot[bot] in https://github.com/actions/runner/pull/3180 +* Bump typescript from 5.2.2 to 5.9.2 in /src/Misc/expressionFunc/hashFiles by @dependabot[bot] in https://github.com/actions/runner/pull/4007 +* chore: migrate Husky config from v8 to v9 format by @salmanmkc in https://github.com/actions/runner/pull/4003 +* Map RUNNER_TEMP for container action by @ericsciple in https://github.com/actions/runner/pull/4011 +* Break UseV2Flow into UseV2Flow and UseRunnerAdminFlow. by @TingluoHuang in https://github.com/actions/runner/pull/4013 +* Update Docker to v28.4.0 and Buildx to v0.28.0 by @github-actions[bot] in https://github.com/actions/runner/pull/4020 +* Bump node.js to latest version in runner. by @TingluoHuang in https://github.com/actions/runner/pull/4022 +* feat: add automated .NET dependency management workflow by @salmanmkc in https://github.com/actions/runner/pull/4028 +* feat: add automated Docker BuildX dependency management workflow by @salmanmkc in https://github.com/actions/runner/pull/4029 +* feat: add automated Node.js version management workflow by @salmanmkc in https://github.com/actions/runner/pull/4026 +* feat: add comprehensive NPM security management workflow by @salmanmkc in https://github.com/actions/runner/pull/4027 +* feat: add comprehensive dependency monitoring system by @salmanmkc in https://github.com/actions/runner/pull/4025 +* Use BrokerURL when using RunnerAdmin by @luketomlinson in https://github.com/actions/runner/pull/4044 +* Bump actions/github-script from 7.0.1 to 8.0.0 by @dependabot[bot] in https://github.com/actions/runner/pull/4016 +* Bump actions/stale from 9 to 10 by @dependabot[bot] in https://github.com/actions/runner/pull/4015 +* fix: prevent Node.js upgrade workflow from creating PRs with empty versions by @salmanmkc in https://github.com/actions/runner/pull/4055 +* chore: update Node versions by @github-actions[bot] in https://github.com/actions/runner/pull/4057 +* Bump actions/setup-node from 4 to 5 by @dependabot[bot] in https://github.com/actions/runner/pull/4037 +* Bump Azure.Storage.Blobs from 12.25.0 to 12.25.1 by @dependabot[bot] in https://github.com/actions/runner/pull/4058 +* Update Docker to v28.5.0 and Buildx to v0.29.1 by @github-actions[bot] in https://github.com/actions/runner/pull/4069 +* Bump github/codeql-action from 3 to 4 by @dependabot[bot] in https://github.com/actions/runner/pull/4072 +* chore: update Node versions by @github-actions[bot] in https://github.com/actions/runner/pull/4075 +* Include k8s novolume (version v0.8.0) by @nikola-jokic in https://github.com/actions/runner/pull/4063 +* Make sure runner-admin has both auth_url and auth_url_v2. by @TingluoHuang in https://github.com/actions/runner/pull/4066 +* Report job has infra failure to run-service by @TingluoHuang in https://github.com/actions/runner/pull/4073 +* Bump actions/setup-node from 5 to 6 by @dependabot[bot] in https://github.com/actions/runner/pull/4078 ## New Contributors -* @Samirat made their first contribution in https://github.com/actions/runner/pull/3954 -* @djs-intel made their first contribution in https://github.com/actions/runner/pull/3972 +* @horner made their first contribution in https://github.com/actions/runner/pull/3157 -**Full Changelog**: https://github.com/actions/runner/compare/v2.327.1...v2.328.0 +**Full Changelog**: https://github.com/actions/runner/compare/v2.328.0...v2.329.0 _Note: Actions Runner follows a progressive release policy, so the latest release might not be available to your enterprise, organization, or repository yet. To confirm which version of the Actions Runner you should expect, please view the download instructions for your enterprise, organization, or repository. diff --git a/src/runnerversion b/src/runnerversion index 2a32b8c5c..793bb0f09 100644 --- a/src/runnerversion +++ b/src/runnerversion @@ -1 +1 @@ -2.328.0 +2.329.0 From 60af948051cbeaf51d87397c40354c1496fcdd8f Mon Sep 17 00:00:00 2001 From: Lawrence Gripper Date: Thu, 16 Oct 2025 21:16:14 +0100 Subject: [PATCH 7/8] Custom Image: Preflight checks (#4081) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/Runner.Common/Constants.cs | 2 + src/Runner.Worker/JobExtension.cs | 4 + .../SnapshotOperationProvider.cs | 27 +++ src/Test/L0/Worker/JobExtensionL0.cs | 188 ++++++++++++++++++ .../L0/Worker/SnapshotOperationProviderL0.cs | 1 + 5 files changed, 222 insertions(+) diff --git a/src/Runner.Common/Constants.cs b/src/Runner.Common/Constants.cs index f3550842b..45ce81e0e 100644 --- a/src/Runner.Common/Constants.cs +++ b/src/Runner.Common/Constants.cs @@ -170,6 +170,8 @@ namespace GitHub.Runner.Common public static readonly string AddCheckRunIdToJobContext = "actions_add_check_run_id_to_job_context"; public static readonly string DisplayHelpfulActionsDownloadErrors = "actions_display_helpful_actions_download_errors"; public static readonly string ContainerActionRunnerTemp = "actions_container_action_runner_temp"; + public static readonly string SnapshotPreflightHostedRunnerCheck = "actions_snapshot_preflight_hosted_runner_check"; + public static readonly string SnapshotPreflightImageGenPoolCheck = "actions_snapshot_preflight_image_gen_pool_check"; } // Node version migration related constants diff --git a/src/Runner.Worker/JobExtension.cs b/src/Runner.Worker/JobExtension.cs index 58e8929b4..14b798e6d 100644 --- a/src/Runner.Worker/JobExtension.cs +++ b/src/Runner.Worker/JobExtension.cs @@ -400,6 +400,10 @@ namespace GitHub.Runner.Worker if (snapshotRequest != null) { var snapshotOperationProvider = HostContext.GetService(); + // Check that that runner is capable of taking a snapshot + snapshotOperationProvider.RunSnapshotPreflightChecks(context); + + // Add postjob step to write snapshot file jobContext.RegisterPostJobStep(new JobExtensionRunner( runAsync: (executionContext, _) => snapshotOperationProvider.CreateSnapshotRequestAsync(executionContext, snapshotRequest), condition: snapshotRequest.Condition, diff --git a/src/Runner.Worker/SnapshotOperationProvider.cs b/src/Runner.Worker/SnapshotOperationProvider.cs index 73630d498..16024e067 100644 --- a/src/Runner.Worker/SnapshotOperationProvider.cs +++ b/src/Runner.Worker/SnapshotOperationProvider.cs @@ -1,15 +1,19 @@ #nullable enable +using System; using System.IO; using System.Threading.Tasks; using GitHub.DistributedTask.Pipelines; +using GitHub.DistributedTask.WebApi; using GitHub.Runner.Common; using GitHub.Runner.Sdk; +using GitHub.Runner.Worker.Handlers; namespace GitHub.Runner.Worker; [ServiceLocator(Default = typeof(SnapshotOperationProvider))] public interface ISnapshotOperationProvider : IRunnerService { Task CreateSnapshotRequestAsync(IExecutionContext executionContext, Snapshot snapshotRequest); + void RunSnapshotPreflightChecks(IExecutionContext jobContext); } public class SnapshotOperationProvider : RunnerService, ISnapshotOperationProvider @@ -24,9 +28,32 @@ public class SnapshotOperationProvider : RunnerService, ISnapshotOperationProvid } IOUtil.SaveObject(snapshotRequest, snapshotRequestFilePath); + executionContext.Output($"Image Name: {snapshotRequest.ImageName} Version: {snapshotRequest.Version}"); executionContext.Output($"Request written to: {snapshotRequestFilePath}"); executionContext.Output("This request will be processed after the job completes. You will not receive any feedback on the snapshot process within the workflow logs of this job."); executionContext.Output("If the snapshot process is successful, you should see a new image with the requested name in the list of available custom images when creating a new GitHub-hosted Runner."); return Task.CompletedTask; } + + public void RunSnapshotPreflightChecks(IExecutionContext context) + { + var shouldCheckRunnerEnvironment = context.Global.Variables.GetBoolean(Constants.Runner.Features.SnapshotPreflightHostedRunnerCheck) ?? false; + if (shouldCheckRunnerEnvironment && + context.Global.Variables.TryGetValue(WellKnownDistributedTaskVariables.RunnerEnvironment, out var runnerEnvironment) && + !string.IsNullOrEmpty(runnerEnvironment)) + { + context.Debug($"Snapshot: RUNNER_ENVIRONMENT={runnerEnvironment}"); + if (!string.Equals(runnerEnvironment, "github-hosted", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("Snapshot workflows must be run on a GitHub Hosted Runner"); + } + } + var imageGenEnabled = StringUtil.ConvertToBoolean(Environment.GetEnvironmentVariable("GITHUB_ACTIONS_IMAGE_GEN_ENABLED")); + context.Debug($"Snapshot: GITHUB_ACTIONS_IMAGE_GEN_ENABLED={imageGenEnabled}"); + var shouldCheckImageGenPool = context.Global.Variables.GetBoolean(Constants.Runner.Features.SnapshotPreflightImageGenPoolCheck) ?? false; + if (shouldCheckImageGenPool && !imageGenEnabled) + { + throw new ArgumentException("Snapshot workflows must be run a hosted runner with Image Generation enabled"); + } + } } diff --git a/src/Test/L0/Worker/JobExtensionL0.cs b/src/Test/L0/Worker/JobExtensionL0.cs index 9ce99070b..60814998e 100644 --- a/src/Test/L0/Worker/JobExtensionL0.cs +++ b/src/Test/L0/Worker/JobExtensionL0.cs @@ -567,5 +567,193 @@ namespace GitHub.Runner.Common.Tests.Worker } } } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async Task SnapshotPreflightChecks_HostedRunnerCheck_Enabled_GitHubHosted_Success() + { + using (TestHostContext hc = CreateTestContext()) + { + _jobEc.Global.Variables.Set(WellKnownDistributedTaskVariables.RunnerEnvironment, "github-hosted"); + + hc.SetSingleton(new SnapshotOperationProvider()); + _jobEc.Global.Variables.Set(Constants.Runner.Features.SnapshotPreflightHostedRunnerCheck, "true"); + + var jobExtension = new JobExtension(); + jobExtension.Initialize(hc); + + + var snapshot = new Pipelines.Snapshot("TestImageNameForPreflightCheck"); + var imageNameValueStringToken = new StringToken(null, null, null, snapshot.ImageName); + _message.Snapshot = imageNameValueStringToken; + + _actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny(), It.IsAny>(), It.IsAny())) + .Returns(Task.FromResult(new PrepareResult(new List(), new Dictionary()))); + + await jobExtension.InitializeJob(_jobEc, _message); + + var postJobSteps = _jobEc.PostJobSteps; + Assert.Equal(1, postJobSteps.Count); + } + + Environment.SetEnvironmentVariable("RUNNER_ENVIRONMENT", null); + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async Task SnapshotPreflightChecks_HostedRunnerCheck_Enabled_SelfHosted_ThrowsException() + { + using (TestHostContext hc = CreateTestContext()) + { + _jobEc.Global.Variables.Set(WellKnownDistributedTaskVariables.RunnerEnvironment, "self-hosted"); + hc.SetSingleton(new SnapshotOperationProvider()); + + var jobExtension = new JobExtension(); + jobExtension.Initialize(hc); + + _jobEc.Global.Variables.Set(Constants.Runner.Features.SnapshotPreflightHostedRunnerCheck, "true"); + + var snapshot = new Pipelines.Snapshot("TestImageNameForPreflightCheck"); + var imageNameValueStringToken = new StringToken(null, null, null, snapshot.ImageName); + _message.Snapshot = imageNameValueStringToken; + + _actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny(), It.IsAny>(), It.IsAny())) + .Returns(Task.FromResult(new PrepareResult(new List(), new Dictionary()))); + + var exception = await Assert.ThrowsAsync(() => jobExtension.InitializeJob(_jobEc, _message)); + Assert.Contains("Snapshot workflows must be run on a GitHub Hosted Runner", exception.Message); + } + + Environment.SetEnvironmentVariable("RUNNER_ENVIRONMENT", null); + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async Task SnapshotPreflightChecks_ImageGenPoolCheck_Enabled_ImageGenEnabled_Success() + { + Environment.SetEnvironmentVariable("GITHUB_ACTIONS_IMAGE_GEN_ENABLED", "true"); + + using (TestHostContext hc = CreateTestContext()) + { + hc.SetSingleton(new SnapshotOperationProvider()); + + var jobExtension = new JobExtension(); + jobExtension.Initialize(hc); + + _jobEc.Global.Variables.Set(Constants.Runner.Features.SnapshotPreflightImageGenPoolCheck, "true"); + + var snapshot = new Pipelines.Snapshot("TestImageNameForPreflightCheck"); + var imageNameValueStringToken = new StringToken(null, null, null, snapshot.ImageName); + _message.Snapshot = imageNameValueStringToken; + + _actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny(), It.IsAny>(), It.IsAny())) + .Returns(Task.FromResult(new PrepareResult(new List(), new Dictionary()))); + + await jobExtension.InitializeJob(_jobEc, _message); + + var postJobSteps = _jobEc.PostJobSteps; + Assert.Equal(1, postJobSteps.Count); + } + + Environment.SetEnvironmentVariable("GITHUB_ACTIONS_IMAGE_GEN_ENABLED", null); + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async Task SnapshotPreflightChecks_ImageGenPoolCheck_Enabled_ImageGen_False_ThrowsException() + { + Environment.SetEnvironmentVariable("GITHUB_ACTIONS_IMAGE_GEN_ENABLED", "false"); + + using (TestHostContext hc = CreateTestContext()) + { + hc.SetSingleton(new SnapshotOperationProvider()); + _jobEc.SetRunnerContext("environment", "github-hosted"); + + var jobExtension = new JobExtension(); + jobExtension.Initialize(hc); + + _jobEc.Global.Variables.Set(Constants.Runner.Features.SnapshotPreflightImageGenPoolCheck, "true"); + + var snapshot = new Pipelines.Snapshot("TestImageNameForPreflightCheck"); + var imageNameValueStringToken = new StringToken(null, null, null, snapshot.ImageName); + _message.Snapshot = imageNameValueStringToken; + + _actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny(), It.IsAny>(), It.IsAny())) + .Returns(Task.FromResult(new PrepareResult(new List(), new Dictionary()))); + + var exception = await Assert.ThrowsAsync(() => jobExtension.InitializeJob(_jobEc, _message)); + Assert.Contains("Snapshot workflows must be run a hosted runner with Image Generation enabled", exception.Message); + } + + Environment.SetEnvironmentVariable("GITHUB_ACTIONS_IMAGE_GEN_ENABLED", null); + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async Task SnapshotPreflightChecks_ImageGenPoolCheck_Enabled_ImageGen_Missing_ThrowsException() + { + using (TestHostContext hc = CreateTestContext()) + { + hc.SetSingleton(new SnapshotOperationProvider()); + + var jobExtension = new JobExtension(); + jobExtension.Initialize(hc); + + _jobEc.Global.Variables.Set(Constants.Runner.Features.SnapshotPreflightImageGenPoolCheck, "true"); + + var snapshot = new Pipelines.Snapshot("TestImageNameForPreflightCheck"); + var imageNameValueStringToken = new StringToken(null, null, null, snapshot.ImageName); + _message.Snapshot = imageNameValueStringToken; + + _actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny(), It.IsAny>(), It.IsAny())) + .Returns(Task.FromResult(new PrepareResult(new List(), new Dictionary()))); + + var exception = await Assert.ThrowsAsync(() => jobExtension.InitializeJob(_jobEc, _message)); + Assert.Contains("Snapshot workflows must be run a hosted runner with Image Generation enabled", exception.Message); + } + + Environment.SetEnvironmentVariable("GITHUB_ACTIONS_IMAGE_GEN_ENABLED", null); + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public async Task SnapshotPreflightChecks_BothChecks_Enabled_AllConditionsMet_Success() + { + Environment.SetEnvironmentVariable("GITHUB_ACTIONS_IMAGE_GEN_ENABLED", "true"); + + using (TestHostContext hc = CreateTestContext()) + { + hc.SetSingleton(new SnapshotOperationProvider()); + + var jobExtension = new JobExtension(); + jobExtension.Initialize(hc); + + // Enable both preflight checks + _jobEc.Global.Variables.Set(WellKnownDistributedTaskVariables.RunnerEnvironment, "github-hosted"); + _jobEc.Global.Variables.Set(Constants.Runner.Features.SnapshotPreflightHostedRunnerCheck, "true"); + _jobEc.Global.Variables.Set(Constants.Runner.Features.SnapshotPreflightImageGenPoolCheck, "true"); + + var snapshot = new Pipelines.Snapshot("TestImageNameForPreflightCheck"); + var imageNameValueStringToken = new StringToken(null, null, null, snapshot.ImageName); + _message.Snapshot = imageNameValueStringToken; + + _actionManager.Setup(x => x.PrepareActionsAsync(It.IsAny(), It.IsAny>(), It.IsAny())) + .Returns(Task.FromResult(new PrepareResult(new List(), new Dictionary()))); + + await jobExtension.InitializeJob(_jobEc, _message); + + var postJobSteps = _jobEc.PostJobSteps; + Assert.Equal(1, postJobSteps.Count); + } + + Environment.SetEnvironmentVariable("RUNNER_ENVIRONMENT", null); + Environment.SetEnvironmentVariable("GITHUB_ACTIONS_IMAGE_GEN_ENABLED", null); + } } } diff --git a/src/Test/L0/Worker/SnapshotOperationProviderL0.cs b/src/Test/L0/Worker/SnapshotOperationProviderL0.cs index 4f747ae8e..d2d5260b6 100644 --- a/src/Test/L0/Worker/SnapshotOperationProviderL0.cs +++ b/src/Test/L0/Worker/SnapshotOperationProviderL0.cs @@ -38,6 +38,7 @@ public class SnapshotOperationProviderL0 Assert.NotNull(actualSnapshot); Assert.Equal(expectedSnapshot.ImageName, actualSnapshot!.ImageName); _ec.Verify(ec => ec.Write(null, $"Request written to: {_snapshotRequestFilePath}"), Times.Once); + _ec.Verify(ec => ec.Write(null, $"Image Name: {expectedSnapshot.ImageName} Version: {expectedSnapshot.Version}"), Times.Once); _ec.Verify(ec => ec.Write(null, "This request will be processed after the job completes. You will not receive any feedback on the snapshot process within the workflow logs of this job."), Times.Once); _ec.Verify(ec => ec.Write(null, "If the snapshot process is successful, you should see a new image with the requested name in the list of available custom images when creating a new GitHub-hosted Runner."), Times.Once); _ec.VerifyNoOtherCalls(); From 0b2c71fc3153bd19ce11d6b40c84ebcd40dfcde4 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 20 Oct 2025 11:40:09 +0100 Subject: [PATCH 8/8] Update dotnet sdk to latest version @8.0.415 (#4080) Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Salman Chishti --- .devcontainer/devcontainer.json | 2 +- src/dev.sh | 2 +- src/global.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 5eb31abc8..6c084cbc1 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -4,7 +4,7 @@ "features": { "ghcr.io/devcontainers/features/docker-in-docker:1": {}, "ghcr.io/devcontainers/features/dotnet": { - "version": "8.0.413" + "version": "8.0.415" }, "ghcr.io/devcontainers/features/node:1": { "version": "20" diff --git a/src/dev.sh b/src/dev.sh index 8457772ab..2ec3c3e6f 100755 --- a/src/dev.sh +++ b/src/dev.sh @@ -17,7 +17,7 @@ LAYOUT_DIR="$SCRIPT_DIR/../_layout" DOWNLOAD_DIR="$SCRIPT_DIR/../_downloads/netcore2x" PACKAGE_DIR="$SCRIPT_DIR/../_package" DOTNETSDK_ROOT="$SCRIPT_DIR/../_dotnetsdk" -DOTNETSDK_VERSION="8.0.413" +DOTNETSDK_VERSION="8.0.415" DOTNETSDK_INSTALLDIR="$DOTNETSDK_ROOT/$DOTNETSDK_VERSION" RUNNER_VERSION=$(cat runnerversion) diff --git a/src/global.json b/src/global.json index 1b22b198f..d23a6248a 100644 --- a/src/global.json +++ b/src/global.json @@ -1,5 +1,5 @@ { "sdk": { - "version": "8.0.413" + "version": "8.0.415" } }