From 395d460f5f0beeece6f6b6807eff04476b2ebe23 Mon Sep 17 00:00:00 2001 From: florentianayuwono Date: Fri, 16 Oct 2026 10:20:07 +0700 Subject: [PATCH 1/2] Fix ARM32 internal tooling defaulting to node24 (no node24 build exists for linux-armv7l) After syncing with upstream v2.338.0, armhf (linux-arm) runners started failing with errors like: Error: An error occurred trying to start process '.../externals/node24/bin/node' ... No such file or directory Root cause: NodeUtil.GetInternalNodeVersion() is used for *internal* runner tooling (hashFiles(), the macOS SIP re-invoker, .js-shell steps, and the NodeJsCheck/CheckUtil self-checks) and unconditionally defaults to node24, with no ARM32 awareness. This is separate from the per-action node version resolution path (HandlerFactory -> CheckNodeVersionForLinuxArm32), which was already patched to fall back to node22 on linux-arm. Node.js upstream no longer publishes linux-armv7l builds for Node 24+, so externals.sh (already patched here) only downloads node20/node22 on linux-arm - node24 is simply not present on disk, hence the failure. Fix: GetInternalNodeVersion() now also falls back to node22 on linux-arm, mirroring the existing CheckNodeVersionForLinuxArm32 pattern, while still honoring an explicit ACTIONS_RUNNER_FORCED_INTERNAL_NODE_VERSION override if set. ppc64le/s390x are unaffected since official node24 builds exist for those platforms. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- patches/runner-main-sdk8-arm.patch | 56 +++++++++++++++++++++++++++--- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/patches/runner-main-sdk8-arm.patch b/patches/runner-main-sdk8-arm.patch index e617e29e804..3fc48a699b9 100644 --- a/patches/runner-main-sdk8-arm.patch +++ b/patches/runner-main-sdk8-arm.patch @@ -57,10 +57,32 @@ index 4ed18b2b..e0c88fbc 100644 win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64 true diff --git a/src/Runner.Common/Util/NodeUtil.cs b/src/Runner.Common/Util/NodeUtil.cs -index 7b8055b9..a40f970d 100644 +index 7b8055b9..584e654a 100644 --- a/src/Runner.Common/Util/NodeUtil.cs +++ b/src/Runner.Common/Util/NodeUtil.cs -@@ -128,6 +128,19 @@ namespace GitHub.Runner.Common.Util +@@ -39,6 +39,21 @@ namespace GitHub.Runner.Common.Util + { + return forcedInternalNodeVersion; + } ++ ++ // Upstream Node.js no longer publishes armv7l (32-bit ARM) builds for Node 24+, ++ // and this fork's Linux ARM32 externals bundle node22 instead of node24 (see ++ // externals.sh and NodeUtil.CheckNodeVersionForLinuxArm32). Internal runner ++ // tooling that resolves its own node binary via this method -- hashFiles(), ++ // the macOS SIP re-invoker, .js-shell steps, and the runner's own self-checks -- ++ // must independently account for that, since none of them go through the ++ // per-action node-version resolution in HandlerFactory/CheckNodeVersionForLinuxArm32. ++ bool isArm32Linux = Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.Arm) && ++ Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux); ++ if (isArm32Linux) ++ { ++ return Constants.Runner.NodeMigration.Node22; ++ } ++ + return _defaultNodeVersion; + } + /// +@@ -128,6 +143,19 @@ namespace GitHub.Runner.Common.Util return (preferredVersion, null); } @@ -80,7 +102,7 @@ index 7b8055b9..a40f970d 100644 // ARM32 kill switch: runner should no longer work on this platform if (killArm32) { -@@ -144,7 +157,7 @@ namespace GitHub.Runner.Common.Util +@@ -144,7 +172,7 @@ namespace GitHub.Runner.Common.Util if (string.Equals(preferredVersion, Constants.Runner.NodeMigration.Node24, StringComparison.OrdinalIgnoreCase)) { @@ -89,7 +111,7 @@ index 7b8055b9..a40f970d 100644 } return (preferredVersion, deprecationWarning); -@@ -153,7 +166,7 @@ namespace GitHub.Runner.Common.Util +@@ -153,7 +181,7 @@ namespace GitHub.Runner.Common.Util // Legacy behavior: fall back to node20 if node24 was requested on ARM32 if (string.Equals(preferredVersion, Constants.Runner.NodeMigration.Node24, StringComparison.OrdinalIgnoreCase)) { @@ -98,7 +120,7 @@ index 7b8055b9..a40f970d 100644 } return (preferredVersion, null); -@@ -194,5 +207,24 @@ namespace GitHub.Runner.Common.Util +@@ -194,5 +222,24 @@ namespace GitHub.Runner.Common.Util return info; } @@ -248,6 +270,30 @@ index 8058f761..90e6cd61 100644 Library win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64 +diff --git a/src/Test/L0/Util/NodeUtilL0.cs b/src/Test/L0/Util/NodeUtilL0.cs +index 0a493c73..15a48afd 100644 +--- a/src/Test/L0/Util/NodeUtilL0.cs ++++ b/src/Test/L0/Util/NodeUtilL0.cs +@@ -11,10 +11,19 @@ namespace GitHub.Runner.Common.Tests.Util + [Theory] + [Trait("Level", "L0")] + [Trait("Category", "Common")] ++#if ARM ++ // Linux ARM32 bundles node22 (not node24) since upstream Node.js no longer ships ++ // armv7l builds for Node 24+; GetInternalNodeVersion must default accordingly. ++ [InlineData(null, "node22")] ++ [InlineData("node24", "node24")] // Explicit operator override still wins ++ [InlineData("node20", "node20")] ++ [InlineData("node16", "node22")] // Unsupported versions fall back to the platform default ++#else + [InlineData(null, "node24")] + [InlineData("node24", "node24")] + [InlineData("node20", "node20")] + [InlineData("node16", "node24")] // Unsupported versions are ignored ++#endif + public void TestInternalNodeVersion(string forcedVersion, string expectedVersion) + { + try diff --git a/src/Test/L0/Worker/HandlerFactoryL0.cs b/src/Test/L0/Worker/HandlerFactoryL0.cs index e8ecdef3..e7e37048 100644 --- a/src/Test/L0/Worker/HandlerFactoryL0.cs From bd7308361a2fb08520d690f128742e05ec4079aa Mon Sep 17 00:00:00 2001 From: florentianayuwono Date: Fri, 16 Oct 2026 10:30:45 +0700 Subject: [PATCH 2/2] Apply internal node version fallback to release-tag ARM patch too As flagged by Copilot review: the shipped runner tarball is built from patches/runner-sdk8-arm.patch (release.yml), not patches/runner-main-sdk8-arm.patch (build.yml, CI-only). The release patch's NodeUtil.cs changes only covered CheckNodeVersionForLinuxArm32 (the per-action path); it was still missing the GetInternalNodeVersion() fallback, so actual released armhf runners would still hit the node24 'No such file or directory' failure. Mirrors the same fix: GetInternalNodeVersion() falls back to node22 on linux-arm, plus the equivalent #if ARM/#else NodeUtilL0 test update. Verified: patch applies cleanly to a vanilla v2.338.0 clone, builds, and 70/70 targeted tests (NodeUtilL0/HandlerFactoryL0/StepHostNodeVersionL0) pass on linux-x64. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- patches/runner-sdk8-arm.patch | 56 +++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 5 deletions(-) diff --git a/patches/runner-sdk8-arm.patch b/patches/runner-sdk8-arm.patch index d86bfd899f3..a46c2d69a46 100644 --- a/patches/runner-sdk8-arm.patch +++ b/patches/runner-sdk8-arm.patch @@ -57,10 +57,32 @@ index 4ed18b2b..e0c88fbc 100644 win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64 true diff --git a/src/Runner.Common/Util/NodeUtil.cs b/src/Runner.Common/Util/NodeUtil.cs -index 7b8055b9..a40f970d 100644 +index 7b8055b9..584e654a 100644 --- a/src/Runner.Common/Util/NodeUtil.cs +++ b/src/Runner.Common/Util/NodeUtil.cs -@@ -128,6 +128,19 @@ namespace GitHub.Runner.Common.Util +@@ -39,6 +39,21 @@ namespace GitHub.Runner.Common.Util + { + return forcedInternalNodeVersion; + } ++ ++ // Upstream Node.js no longer publishes armv7l (32-bit ARM) builds for Node 24+, ++ // and this fork's Linux ARM32 externals bundle node22 instead of node24 (see ++ // externals.sh and NodeUtil.CheckNodeVersionForLinuxArm32). Internal runner ++ // tooling that resolves its own node binary via this method -- hashFiles(), ++ // the macOS SIP re-invoker, .js-shell steps, and the runner's own self-checks -- ++ // must independently account for that, since none of them go through the ++ // per-action node-version resolution in HandlerFactory/CheckNodeVersionForLinuxArm32. ++ bool isArm32Linux = Constants.Runner.PlatformArchitecture.Equals(Constants.Architecture.Arm) && ++ Constants.Runner.Platform.Equals(Constants.OSPlatform.Linux); ++ if (isArm32Linux) ++ { ++ return Constants.Runner.NodeMigration.Node22; ++ } ++ + return _defaultNodeVersion; + } + /// +@@ -128,6 +143,19 @@ namespace GitHub.Runner.Common.Util return (preferredVersion, null); } @@ -80,7 +102,7 @@ index 7b8055b9..a40f970d 100644 // ARM32 kill switch: runner should no longer work on this platform if (killArm32) { -@@ -144,7 +157,7 @@ namespace GitHub.Runner.Common.Util +@@ -144,7 +172,7 @@ namespace GitHub.Runner.Common.Util if (string.Equals(preferredVersion, Constants.Runner.NodeMigration.Node24, StringComparison.OrdinalIgnoreCase)) { @@ -89,7 +111,7 @@ index 7b8055b9..a40f970d 100644 } return (preferredVersion, deprecationWarning); -@@ -153,7 +166,7 @@ namespace GitHub.Runner.Common.Util +@@ -153,7 +181,7 @@ namespace GitHub.Runner.Common.Util // Legacy behavior: fall back to node20 if node24 was requested on ARM32 if (string.Equals(preferredVersion, Constants.Runner.NodeMigration.Node24, StringComparison.OrdinalIgnoreCase)) { @@ -98,7 +120,7 @@ index 7b8055b9..a40f970d 100644 } return (preferredVersion, null); -@@ -194,5 +207,24 @@ namespace GitHub.Runner.Common.Util +@@ -194,5 +222,24 @@ namespace GitHub.Runner.Common.Util return info; } @@ -248,6 +270,30 @@ index e6ba57af..0b5b1d62 100644 Library win-x64;win-x86;linux-x64;linux-arm64;linux-arm;osx-x64;osx-arm64;win-arm64 +diff --git a/src/Test/L0/Util/NodeUtilL0.cs b/src/Test/L0/Util/NodeUtilL0.cs +index 0a493c73..15a48afd 100644 +--- a/src/Test/L0/Util/NodeUtilL0.cs ++++ b/src/Test/L0/Util/NodeUtilL0.cs +@@ -11,10 +11,19 @@ namespace GitHub.Runner.Common.Tests.Util + [Theory] + [Trait("Level", "L0")] + [Trait("Category", "Common")] ++#if ARM ++ // Linux ARM32 bundles node22 (not node24) since upstream Node.js no longer ships ++ // armv7l builds for Node 24+; GetInternalNodeVersion must default accordingly. ++ [InlineData(null, "node22")] ++ [InlineData("node24", "node24")] // Explicit operator override still wins ++ [InlineData("node20", "node20")] ++ [InlineData("node16", "node22")] // Unsupported versions fall back to the platform default ++#else + [InlineData(null, "node24")] + [InlineData("node24", "node24")] + [InlineData("node20", "node20")] + [InlineData("node16", "node24")] // Unsupported versions are ignored ++#endif + public void TestInternalNodeVersion(string forcedVersion, string expectedVersion) + { + try diff --git a/src/Test/L0/Worker/HandlerFactoryL0.cs b/src/Test/L0/Worker/HandlerFactoryL0.cs index e8ecdef3..e7e37048 100644 --- a/src/Test/L0/Worker/HandlerFactoryL0.cs