From df2cfa23685cf5ed727b6f1e4e9e4c7e5168991c Mon Sep 17 00:00:00 2001 From: Surajit Shil Date: Wed, 15 Oct 2025 16:54:35 +0530 Subject: [PATCH 01/13] Fixing codebase to support upgarde to Node24 --- src/Agent.Sdk/Knob/AgentKnobs.cs | 7 ++ src/Agent.Worker/Handlers/NodeHandler.cs | 118 ++++++++++++++++++++--- src/Agent.Worker/TaskManager.cs | 37 +++++-- 3 files changed, 140 insertions(+), 22 deletions(-) diff --git a/src/Agent.Sdk/Knob/AgentKnobs.cs b/src/Agent.Sdk/Knob/AgentKnobs.cs index 8338d07780..63732f1b20 100644 --- a/src/Agent.Sdk/Knob/AgentKnobs.cs +++ b/src/Agent.Sdk/Knob/AgentKnobs.cs @@ -200,6 +200,13 @@ public class AgentKnobs new EnvironmentKnobSource("AGENT_USE_NODE20_IN_UNSUPPORTED_SYSTEM"), new BuiltInDefaultKnobSource("false")); + public static readonly Knob UseNode24 = new Knob( + nameof(UseNode24), + "Forces the agent to use Node 24 handler for all Node-based tasks", + new RuntimeKnobSource("AGENT_USE_NODE24"), + new EnvironmentKnobSource("AGENT_USE_NODE24"), + new BuiltInDefaultKnobSource("false")); + public static readonly Knob FetchByCommitForFullClone = new Knob( nameof(FetchByCommitForFullClone), "If true, allow fetch by commit when doing a full clone (depth=0).", diff --git a/src/Agent.Worker/Handlers/NodeHandler.cs b/src/Agent.Worker/Handlers/NodeHandler.cs index adabc5b51d..938d8d9d2a 100644 --- a/src/Agent.Worker/Handlers/NodeHandler.cs +++ b/src/Agent.Worker/Handlers/NodeHandler.cs @@ -59,10 +59,11 @@ public sealed class NodeHandler : Handler, INodeHandler internal const string NodeFolder = "node"; internal static readonly string Node16Folder = "node16"; internal static readonly string Node20_1Folder = "node20_1"; + internal static readonly string Node24Folder = "node24"; private static readonly string nodeLTS = Node16Folder; private const string useNodeKnobLtsKey = "LTS"; private const string useNodeKnobUpgradeKey = "UPGRADE"; - private string[] possibleNodeFolders = { NodeFolder, node10Folder, Node16Folder, Node20_1Folder }; + private string[] possibleNodeFolders = { NodeFolder, node10Folder, Node16Folder, Node20_1Folder, Node24Folder }; private static Regex _vstsTaskLibVersionNeedsFix = new Regex("^[0-2]\\.[0-9]+", RegexOptions.Compiled | RegexOptions.IgnoreCase); private static string[] _extensionsNode6 ={ "if (process.versions.node && process.versions.node.match(/^5\\./)) {", @@ -83,6 +84,7 @@ public sealed class NodeHandler : Handler, INodeHandler "};" }; private bool? supportsNode20; + private bool? supportsNode24; public NodeHandler() { @@ -190,31 +192,48 @@ public async Task RunAsync() { bool useNode20InUnsupportedSystem = AgentKnobs.UseNode20InUnsupportedSystem.GetValue(ExecutionContext).AsBoolean(); bool node20ResultsInGlibCErrorHost = false; + bool node24ResultsInGlibCErrorHost = false; - if (PlatformUtil.HostOS == PlatformUtil.OS.Linux && !useNode20InUnsupportedSystem) + // Separate glibc checks for each Node version + if (PlatformUtil.HostOS == PlatformUtil.OS.Linux) { - if (supportsNode20.HasValue) + // Node20 glibc check (controlled by its specific knob) + if (!useNode20InUnsupportedSystem) { - node20ResultsInGlibCErrorHost = supportsNode20.Value; + if (supportsNode20.HasValue) + { + node20ResultsInGlibCErrorHost = supportsNode20.Value; + } + else + { + node20ResultsInGlibCErrorHost = await CheckIfNode20ResultsInGlibCError(); + ExecutionContext.EmitHostNode20FallbackTelemetry(node20ResultsInGlibCErrorHost); + supportsNode20 = node20ResultsInGlibCErrorHost; + } + } + + // Node24 glibc check (independent of Node20 knob) + // TODO: Consider adding UseNode24InUnsupportedSystem knob if needed + if (supportsNode24.HasValue) + { + node24ResultsInGlibCErrorHost = !supportsNode24.Value; } else { - node20ResultsInGlibCErrorHost = await CheckIfNode20ResultsInGlibCError(); - - ExecutionContext.EmitHostNode20FallbackTelemetry(node20ResultsInGlibCErrorHost); - - supportsNode20 = node20ResultsInGlibCErrorHost; + node24ResultsInGlibCErrorHost = await CheckIfNode24ResultsInGlibCError(); + // ExecutionContext.EmitHostNode24FallbackTelemetry(node24ResultsInGlibCErrorHost); // Add this method + supportsNode24 = !node24ResultsInGlibCErrorHost; } } ContainerInfo container = (ExecutionContext.StepTarget() as ContainerInfo); if (container == null) { - file = GetNodeLocation(node20ResultsInGlibCErrorHost, inContainer: false); + file = GetNodeLocation(node20ResultsInGlibCErrorHost, node24ResultsInGlibCErrorHost, inContainer: false); } else { - file = GetNodeLocation(container.NeedsNode16Redirect, inContainer: true); + file = GetNodeLocation(container.NeedsNode16Redirect, false, inContainer: true); } ExecutionContext.Debug("Using node path: " + file); @@ -308,20 +327,52 @@ private async Task CheckIfNode20ResultsInGlibCError() return node20ResultsInGlibCError; } + private async Task CheckIfNode24ResultsInGlibCError() + { + var node24 = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeHandler.Node24Folder, "bin", $"node{IOUtil.ExeExtension}"); + List nodeVersionOutput = await ExecuteCommandAsync(ExecutionContext, node24, "-v", requireZeroExitCode: false, showOutputOnFailureOnly: true); + var node24ResultsInGlibCError = WorkerUtilities.IsCommandResultGlibcError(ExecutionContext, nodeVersionOutput, out string nodeInfoLine); + + return node24ResultsInGlibCError; + } - public string GetNodeLocation(bool node20ResultsInGlibCError, bool inContainer) + public string GetNodeLocation(bool node20ResultsInGlibCError, bool node24ResultsInGlibCError, bool inContainer) { bool useNode10 = AgentKnobs.UseNode10.GetValue(ExecutionContext).AsBoolean(); bool useNode20_1 = AgentKnobs.UseNode20_1.GetValue(ExecutionContext).AsBoolean(); bool UseNode20InUnsupportedSystem = AgentKnobs.UseNode20InUnsupportedSystem.GetValue(ExecutionContext).AsBoolean(); + bool useNode24 = AgentKnobs.UseNode24.GetValue(ExecutionContext).AsBoolean(); bool taskHasNode10Data = Data is Node10HandlerData; bool taskHasNode16Data = Data is Node16HandlerData; bool taskHasNode20_1Data = Data is Node20_1HandlerData; + bool taskHasNode24Data = Data is Node24HandlerData; string useNodeKnob = AgentKnobs.UseNode.GetValue(ExecutionContext).AsString(); string nodeFolder = NodeHandler.NodeFolder; + if (taskHasNode24Data) + { + Trace.Info($"Task.json has node24 handler data: {taskHasNode24Data}"); - if (taskHasNode20_1Data) + if (node24ResultsInGlibCError) + { + // Fallback to Node20, then Node16 if Node20 also fails + if (node20ResultsInGlibCError) + { + nodeFolder = NodeHandler.Node16Folder; + Node16FallbackWarning(inContainer); + } + else + { + nodeFolder = NodeHandler.Node20_1Folder; + Node20FallbackWarning(inContainer); + } + } + else + { + nodeFolder = NodeHandler.Node24Folder; + } + } + else if (taskHasNode20_1Data) { Trace.Info($"Task.json has node20_1 handler data: {taskHasNode20_1Data} node20ResultsInGlibCError = {node20ResultsInGlibCError}"); @@ -351,6 +402,29 @@ public string GetNodeLocation(bool node20ResultsInGlibCError, bool inContainer) nodeFolder = NodeHandler.node10Folder; } + if (useNode24) + { + Trace.Info($"Found UseNode24 knob, using node24 for node tasks: {useNode24}"); + + if (node24ResultsInGlibCError) + { + // Fallback to Node20, then Node16 if Node20 also fails + if (node20ResultsInGlibCError) + { + nodeFolder = NodeHandler.Node16Folder; + Node16FallbackWarning(inContainer); + } + else + { + nodeFolder = NodeHandler.Node20_1Folder; + Node20FallbackWarning(inContainer); + } + } + else + { + nodeFolder = NodeHandler.Node24Folder; + } + } if (useNode20_1) { Trace.Info($"Found UseNode20_1 knob, using node20_1 for node tasks {useNode20_1} node20ResultsInGlibCError = {node20ResultsInGlibCError}"); @@ -445,6 +519,23 @@ private void Node16FallbackWarning(bool inContainer) } } + + // Add Node20 fallback warning method + private void Node20FallbackWarning(bool inContainer) + { + if (inContainer) + { + ExecutionContext.Warning($"The container operating system doesn't support Node24. Using Node20 instead. " + + "Please upgrade the operating system of the container to remain compatible with future updates of tasks."); + } + else + { + ExecutionContext.Warning($"The agent operating system doesn't support Node24. Using Node20 instead. " + + "Please upgrade the operating system of the agent to remain compatible with future updates of tasks."); + } + } + + private void OnDataReceived(object sender, ProcessDataReceivedEventArgs e) { // drop any outputs after the task get force completed. @@ -575,6 +666,7 @@ private void PublishHandlerTelemetry(string realHandler) string expectedHandler = ""; expectedHandler = Data switch { + Node24HandlerData => "Node24", Node20_1HandlerData => "Node20", Node16HandlerData => "Node16", Node10HandlerData => "Node10", diff --git a/src/Agent.Worker/TaskManager.cs b/src/Agent.Worker/TaskManager.cs index 1bd5659583..6e6e379bff 100644 --- a/src/Agent.Worker/TaskManager.cs +++ b/src/Agent.Worker/TaskManager.cs @@ -376,7 +376,7 @@ private void CheckForTaskDeprecation(IExecutionContext executionContext, Pipelin private void CheckIfTaskNodeRunnerIsDeprecated(IExecutionContext executionContext, Pipelines.TaskStepDefinitionReference task) { string[] deprecatedNodeRunners = { "Node", "Node10", "Node16" }; - string[] approvedNodeRunners = { "Node20_1" }; // Node runners which are not considered as deprecated + string[] approvedNodeRunners = { "Node20_1", "Node24" }; // Node runners which are not considered as deprecated string[] executionSteps = { "prejobexecution", "execution", "postjobexecution" }; JObject taskJson = GetTaskJson(task); @@ -615,6 +615,7 @@ public sealed class ExecutionData private Node10HandlerData _node10; private Node16HandlerData _node16; private Node20_1HandlerData _node20_1; + private Node24HandlerData _node24; private PowerShellHandlerData _powerShell; private PowerShell3HandlerData _powerShell3; private PowerShellExeHandlerData _powerShellExe; @@ -698,6 +699,20 @@ public Node20_1HandlerData Node20_1 } } + public Node24HandlerData Node24 + { + get + { + return _node24; + } + + set + { + _node24 = value; + Add(value); + } + } + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public PowerShellHandlerData PowerShell { @@ -872,25 +887,29 @@ public string WorkingDirectory public sealed class NodeHandlerData : BaseNodeHandlerData { - public override int Priority => 4; + public override int Priority => 5; } public sealed class Node10HandlerData : BaseNodeHandlerData { - public override int Priority => 3; + public override int Priority => 4; } public sealed class Node16HandlerData : BaseNodeHandlerData { - public override int Priority => 2; + public override int Priority => 3; } public sealed class Node20_1HandlerData : BaseNodeHandlerData + { + public override int Priority => 2; + } + public sealed class Node24HandlerData : BaseNodeHandlerData { public override int Priority => 1; } public sealed class PowerShell3HandlerData : HandlerData { - public override int Priority => 5; + public override int Priority => 6; } public sealed class PowerShellHandlerData : HandlerData @@ -908,7 +927,7 @@ public string ArgumentFormat } } - public override int Priority => 6; + public override int Priority => 7; public string WorkingDirectory { @@ -939,7 +958,7 @@ public string ArgumentFormat } } - public override int Priority => 7; + public override int Priority => 8; public string WorkingDirectory { @@ -996,7 +1015,7 @@ public string InlineScript } } - public override int Priority => 7; + public override int Priority => 8; public string ScriptType { @@ -1053,7 +1072,7 @@ public string ModifyEnvironment } } - public override int Priority => 8; + public override int Priority => 9; public string WorkingDirectory { From cd6764da9958abc2ff76779665334acd8449b0c2 Mon Sep 17 00:00:00 2001 From: Surajit Shil Date: Wed, 15 Oct 2025 18:20:13 +0530 Subject: [PATCH 02/13] Adding telemetry for Node24 --- src/Agent.Worker/ExecutionContext.cs | 14 ++++++++++++++ src/Agent.Worker/Handlers/NodeHandler.cs | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Agent.Worker/ExecutionContext.cs b/src/Agent.Worker/ExecutionContext.cs index 2aeae764db..19554591c7 100644 --- a/src/Agent.Worker/ExecutionContext.cs +++ b/src/Agent.Worker/ExecutionContext.cs @@ -96,6 +96,7 @@ public interface IExecutionContext : IAgentService, IKnobValueContext /// void CancelForceTaskCompletion(); void EmitHostNode20FallbackTelemetry(bool node20ResultsInGlibCErrorHost); + void EmitHostNode24FallbackTelemetry(bool node24ResultsInGlibCErrorHost); void PublishTaskRunnerTelemetry(Dictionary taskRunnerData); } @@ -132,6 +133,7 @@ public sealed class ExecutionContext : AgentService, IExecutionContext, ICorrela private FileStream _buildLogsData; private StreamWriter _buildLogsWriter; private bool emittedHostNode20FallbackTelemetry = false; + private bool emittedHostNode24FallbackTelemetry = false; // only job level ExecutionContext will track throttling delay. private long _totalThrottlingDelayInMilliseconds = 0; @@ -973,6 +975,18 @@ public void EmitHostNode20FallbackTelemetry(bool node20ResultsInGlibCErrorHost) emittedHostNode20FallbackTelemetry = true; } } + public void EmitHostNode24FallbackTelemetry(bool node24ResultsInGlibCErrorHost) + { + if (!emittedHostNode24FallbackTelemetry) + { + PublishTelemetry(new Dictionary + { + { "HostNode24to20Fallback", node24ResultsInGlibCErrorHost.ToString() } + }); + + emittedHostNode24FallbackTelemetry = true; + } + } // This overload is to handle specific types some other way. private void PublishTelemetry( diff --git a/src/Agent.Worker/Handlers/NodeHandler.cs b/src/Agent.Worker/Handlers/NodeHandler.cs index 938d8d9d2a..5d4aeb0f33 100644 --- a/src/Agent.Worker/Handlers/NodeHandler.cs +++ b/src/Agent.Worker/Handlers/NodeHandler.cs @@ -221,7 +221,7 @@ public async Task RunAsync() else { node24ResultsInGlibCErrorHost = await CheckIfNode24ResultsInGlibCError(); - // ExecutionContext.EmitHostNode24FallbackTelemetry(node24ResultsInGlibCErrorHost); // Add this method + ExecutionContext.EmitHostNode24FallbackTelemetry(node24ResultsInGlibCErrorHost); // Add this method supportsNode24 = !node24ResultsInGlibCErrorHost; } } From 64b6d12c6726102712ff8bf5183e0d0425071c9a Mon Sep 17 00:00:00 2001 From: Surajit Shil Date: Thu, 16 Oct 2025 18:05:41 +0530 Subject: [PATCH 03/13] Fixing the container logic to support Node24 --- src/Agent.Sdk/ContainerInfo.cs | 1 + src/Agent.Sdk/Knob/AgentKnobs.cs | 12 ++++ .../ContainerOperationProvider.cs | 67 +++++++++++++++++-- src/Agent.Worker/Handlers/NodeHandler.cs | 29 ++++---- 4 files changed, 92 insertions(+), 17 deletions(-) diff --git a/src/Agent.Sdk/ContainerInfo.cs b/src/Agent.Sdk/ContainerInfo.cs index 8e7433211d..89f1500283 100644 --- a/src/Agent.Sdk/ContainerInfo.cs +++ b/src/Agent.Sdk/ContainerInfo.cs @@ -94,6 +94,7 @@ public ContainerInfo(Pipelines.ContainerResource container, Boolean isJobContain public bool IsJobContainer { get; set; } public bool MapDockerSocket { get; set; } public bool NeedsNode16Redirect { get; set; } + public bool NeedsNode20Redirect { get; set; } public PlatformUtil.OS ImageOS { get diff --git a/src/Agent.Sdk/Knob/AgentKnobs.cs b/src/Agent.Sdk/Knob/AgentKnobs.cs index 63732f1b20..7c95543d3c 100644 --- a/src/Agent.Sdk/Knob/AgentKnobs.cs +++ b/src/Agent.Sdk/Knob/AgentKnobs.cs @@ -206,6 +206,12 @@ public class AgentKnobs new RuntimeKnobSource("AGENT_USE_NODE24"), new EnvironmentKnobSource("AGENT_USE_NODE24"), new BuiltInDefaultKnobSource("false")); + public static readonly Knob UseNode24InUnsupportedSystem = new Knob( + nameof(UseNode24InUnsupportedSystem), + "Forces the agent to use Node 24 handler for all Node-based tasks, even if it's in an unsupported system", + new RuntimeKnobSource("AGENT_USE_NODE24_IN_UNSUPPORTED_SYSTEM"), + new EnvironmentKnobSource("AGENT_USE_NODE24_IN_UNSUPPORTED_SYSTEM"), + new BuiltInDefaultKnobSource("false")); public static readonly Knob FetchByCommitForFullClone = new Knob( nameof(FetchByCommitForFullClone), @@ -717,6 +723,12 @@ public class AgentKnobs new RuntimeKnobSource("AZP_AGENT_USE_NODE20_TO_START_CONTAINER"), new PipelineFeatureSource("UseNode20ToStartContainer"), new BuiltInDefaultKnobSource("false")); + public static readonly Knob UseNode24ToStartContainer = new Knob( + nameof(UseNode24ToStartContainer), + "If true, try to start container job using Node24, then fallback to Node20, then Node16.", + new RuntimeKnobSource("AZP_AGENT_USE_NODE24_TO_START_CONTAINER"), + new PipelineFeatureSource("UseNode24ToStartContainer"), + new BuiltInDefaultKnobSource("false")); public static readonly Knob EnableNewMaskerAndRegexes = new Knob( nameof(EnableNewMaskerAndRegexes), diff --git a/src/Agent.Worker/ContainerOperationProvider.cs b/src/Agent.Worker/ContainerOperationProvider.cs index 2f9aae5e8d..debc04aacf 100644 --- a/src/Agent.Worker/ContainerOperationProvider.cs +++ b/src/Agent.Worker/ContainerOperationProvider.cs @@ -526,8 +526,10 @@ private async Task StartContainerAsync(IExecutionContext executionContext, Conta } bool useNode20ToStartContainer = AgentKnobs.UseNode20ToStartContainer.GetValue(executionContext).AsBoolean(); + bool useNode24ToStartContainer = AgentKnobs.UseNode24ToStartContainer.GetValue(executionContext).AsBoolean(); bool useAgentNode = false; + string labelContainerStartupUsingNode24 = "container-startup-using-node-24"; string labelContainerStartupUsingNode20 = "container-startup-using-node-20"; string labelContainerStartupUsingNode16 = "container-startup-using-node-16"; string labelContainerStartupFailed = "container-startup-failed"; @@ -540,6 +542,7 @@ string containerNodePath(string nodeFolder) string nodeContainerPath = containerNodePath(NodeHandler.NodeFolder); string node16ContainerPath = containerNodePath(NodeHandler.Node16Folder); string node20ContainerPath = containerNodePath(NodeHandler.Node20_1Folder); + string node24ContainerPath = containerNodePath(NodeHandler.Node24Folder); if (container.IsJobContainer) { @@ -573,7 +576,20 @@ string useDoubleQuotes(string value) else { useAgentNode = true; - string sleepCommand = useNode20ToStartContainer ? $"'{node20ContainerPath}' --version && echo '{labelContainerStartupUsingNode20}' && {nodeSetInterval(node20ContainerPath)} || '{node16ContainerPath}' --version && echo '{labelContainerStartupUsingNode16}' && {nodeSetInterval(node16ContainerPath)} || echo '{labelContainerStartupFailed}'" : nodeSetInterval(nodeContainerPath); + string sleepCommand; + + if (useNode24ToStartContainer) + { + sleepCommand = $"'{node24ContainerPath}' --version && echo '{labelContainerStartupUsingNode24}' && {nodeSetInterval(node24ContainerPath)} || '{node20ContainerPath}' --version && echo '{labelContainerStartupUsingNode20}' && {nodeSetInterval(node20ContainerPath)} || '{node16ContainerPath}' --version && echo '{labelContainerStartupUsingNode16}' && {nodeSetInterval(node16ContainerPath)} || echo '{labelContainerStartupFailed}'"; + } + else if (useNode20ToStartContainer) + { + sleepCommand = $"'{node20ContainerPath}' --version && echo '{labelContainerStartupUsingNode20}' && {nodeSetInterval(node20ContainerPath)} || '{node16ContainerPath}' --version && echo '{labelContainerStartupUsingNode16}' && {nodeSetInterval(node16ContainerPath)} || echo '{labelContainerStartupFailed}'"; + } + else + { + sleepCommand = nodeSetInterval(nodeContainerPath); + } container.ContainerCommand = PlatformUtil.RunningOnWindows ? $"cmd.exe /c call {useDoubleQuotes(sleepCommand)}" : $"bash -c \"{sleepCommand}\""; container.ResultNodePath = nodeContainerPath; } @@ -609,7 +625,7 @@ string useDoubleQuotes(string value) executionContext.Warning($"Docker container {container.ContainerId} is not in running state."); } - else if (useAgentNode && useNode20ToStartContainer) + else if (useAgentNode && (useNode20ToStartContainer || useNode24ToStartContainer)) { bool containerStartupCompleted = false; int containerStartupTimeoutInMilliseconds = 10000; @@ -622,7 +638,14 @@ string useDoubleQuotes(string value) foreach (string logLine in containerLogs) { - if (logLine.Contains(labelContainerStartupUsingNode20)) + if (logLine.Contains(labelContainerStartupUsingNode24)) // NEW + { + executionContext.Debug("Using Node 24 for container startup."); + containerStartupCompleted = true; + container.ResultNodePath = node24ContainerPath; + break; + } + else if (logLine.Contains(labelContainerStartupUsingNode20)) { executionContext.Debug("Using Node 20 for container startup."); containerStartupCompleted = true; @@ -931,8 +954,30 @@ string useDoubleQuotes(string value) if (PlatformUtil.RunningOnLinux) { bool useNode20InUnsupportedSystem = AgentKnobs.UseNode20InUnsupportedSystem.GetValue(executionContext).AsBoolean(); + bool useNode24InUnsupportedSystem = AgentKnobs.UseNode24InUnsupportedSystem.GetValue(executionContext).AsBoolean(); // NEW knob - if (!useNode20InUnsupportedSystem) + // NEW: Node24 glibc detection (BEFORE Node20) + if (!useNode24InUnsupportedSystem) + { + var node24 = container.TranslateToContainerPath(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeHandler.Node24Folder, "bin", $"node{IOUtil.ExeExtension}")); + + string node24TestCmd = $"bash -c \"{node24} -v\""; + List node24VersionOutput = await DockerExec(executionContext, container.ContainerId, node24TestCmd, noExceptionOnError: true); + + container.NeedsNode20Redirect = WorkerUtilities.IsCommandResultGlibcError(executionContext, node24VersionOutput, out string node24InfoLine); + + if (container.NeedsNode20Redirect) + { + PublishTelemetry( + executionContext, + new Dictionary + { + { "ContainerNode24to20Fallback", container.NeedsNode20Redirect.ToString() } + } + ); + } + } + else if (!useNode20InUnsupportedSystem) { var node20 = container.TranslateToContainerPath(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeHandler.Node20_1Folder, "bin", $"node{IOUtil.ExeExtension}")); @@ -953,6 +998,20 @@ string useDoubleQuotes(string value) } } + // Store the successful Node version path for later use + if (!container.NeedsNode20Redirect) + { + container.ResultNodePath = container.TranslateToContainerPath(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeHandler.Node24Folder, "bin", $"node{IOUtil.ExeExtension}")); // Node24 works + } + else if (!container.NeedsNode16Redirect) + { + container.ResultNodePath = container.TranslateToContainerPath(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeHandler.Node20_1Folder, "bin", $"node{IOUtil.ExeExtension}")); + } + else + { + container.ResultNodePath = container.TranslateToContainerPath(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeHandler.Node16Folder, "bin", $"node{IOUtil.ExeExtension}")); + } + } if (!string.IsNullOrEmpty(containerUserName)) diff --git a/src/Agent.Worker/Handlers/NodeHandler.cs b/src/Agent.Worker/Handlers/NodeHandler.cs index 5d4aeb0f33..ac36dcd3f5 100644 --- a/src/Agent.Worker/Handlers/NodeHandler.cs +++ b/src/Agent.Worker/Handlers/NodeHandler.cs @@ -20,7 +20,7 @@ namespace Microsoft.VisualStudio.Services.Agent.Worker.Handlers [ServiceLocator(Default = typeof(NodeHandler))] public interface INodeHandler : IHandler { - // Data can be of these four types: NodeHandlerData, Node10HandlerData, Node16HandlerData and Node20_1HandlerData + // Data can be of these five types: NodeHandlerData, Node10HandlerData, Node16HandlerData, Node20_1HandlerData and Node24HandlerData BaseNodeHandlerData Data { get; set; } } @@ -191,6 +191,7 @@ public async Task RunAsync() else { bool useNode20InUnsupportedSystem = AgentKnobs.UseNode20InUnsupportedSystem.GetValue(ExecutionContext).AsBoolean(); + bool useNode24InUnsupportedSystem = AgentKnobs.UseNode24InUnsupportedSystem.GetValue(ExecutionContext).AsBoolean(); bool node20ResultsInGlibCErrorHost = false; bool node24ResultsInGlibCErrorHost = false; @@ -202,27 +203,29 @@ public async Task RunAsync() { if (supportsNode20.HasValue) { - node20ResultsInGlibCErrorHost = supportsNode20.Value; + node20ResultsInGlibCErrorHost = !supportsNode20.Value; } else { node20ResultsInGlibCErrorHost = await CheckIfNode20ResultsInGlibCError(); ExecutionContext.EmitHostNode20FallbackTelemetry(node20ResultsInGlibCErrorHost); - supportsNode20 = node20ResultsInGlibCErrorHost; + supportsNode20 = !node20ResultsInGlibCErrorHost; } } // Node24 glibc check (independent of Node20 knob) - // TODO: Consider adding UseNode24InUnsupportedSystem knob if needed - if (supportsNode24.HasValue) + if (!useNode24InUnsupportedSystem) { - node24ResultsInGlibCErrorHost = !supportsNode24.Value; - } - else - { - node24ResultsInGlibCErrorHost = await CheckIfNode24ResultsInGlibCError(); - ExecutionContext.EmitHostNode24FallbackTelemetry(node24ResultsInGlibCErrorHost); // Add this method - supportsNode24 = !node24ResultsInGlibCErrorHost; + if (supportsNode24.HasValue) + { + node24ResultsInGlibCErrorHost = !supportsNode24.Value; + } + else + { + node24ResultsInGlibCErrorHost = await CheckIfNode24ResultsInGlibCError(); + ExecutionContext.EmitHostNode24FallbackTelemetry(node24ResultsInGlibCErrorHost); // Add this method + supportsNode24 = !node24ResultsInGlibCErrorHost; + } } } @@ -233,7 +236,7 @@ public async Task RunAsync() } else { - file = GetNodeLocation(container.NeedsNode16Redirect, false, inContainer: true); + file = GetNodeLocation(container.NeedsNode16Redirect, container.NeedsNode20Redirect, inContainer: true); } ExecutionContext.Debug("Using node path: " + file); From bc518bdf54413e0416a9d4e03bf8106998fdf3a2 Mon Sep 17 00:00:00 2001 From: Surajit Shil Date: Fri, 17 Oct 2025 12:18:22 +0530 Subject: [PATCH 04/13] Updating the externals.sh for node24 --- src/Misc/externals.sh | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/Misc/externals.sh b/src/Misc/externals.sh index 7b682caf4c..1777e2c313 100644 --- a/src/Misc/externals.sh +++ b/src/Misc/externals.sh @@ -27,6 +27,7 @@ NODE10_VERSION="10.24.1" NODE16_VERSION="16.20.2" NODE16_WIN_ARM64_VERSION="16.9.1" NODE20_VERSION="20.19.4" +NODE24_VERSION="24.10.0" MINGIT_VERSION="2.50.1" LFS_VERSION="3.4.0" @@ -201,6 +202,8 @@ if [[ "$PACKAGERUNTIME" == "win-x"* ]]; then acquireExternalTool "${NODE_URL}/v${NODE16_VERSION}/${PACKAGERUNTIME}/node.lib" node16/bin acquireExternalTool "${NODE_URL}/v${NODE20_VERSION}/${PACKAGERUNTIME}/node.exe" node20_1/bin acquireExternalTool "${NODE_URL}/v${NODE20_VERSION}/${PACKAGERUNTIME}/node.lib" node20_1/bin + acquireExternalTool "${NODE_URL}/v${NODE24_VERSION}/${PACKAGERUNTIME}/node.exe" node24/bin + acquireExternalTool "${NODE_URL}/v${NODE24_VERSION}/${PACKAGERUNTIME}/node.lib" node24/bin elif [[ "$PACKAGERUNTIME" == "win-arm64" || "$PACKAGERUNTIME" == "win-arm32" ]]; then # Download external tools for Windows ARM @@ -242,6 +245,10 @@ elif [[ "$PACKAGERUNTIME" == "win-arm64" || "$PACKAGERUNTIME" == "win-arm32" ]]; # Official distribution of Node contains Node 20 for Windows ARM acquireExternalTool "${NODE_URL}/v${NODE20_VERSION}/${PACKAGERUNTIME}/node.exe" node20_1/bin acquireExternalTool "${NODE_URL}/v${NODE20_VERSION}/${PACKAGERUNTIME}/node.lib" node20_1/bin + + # Official distribution of Node contains Node 24 for Windows ARM + acquireExternalTool "${NODE_URL}/v${NODE24_VERSION}/${PACKAGERUNTIME}/node.exe" node24/bin + acquireExternalTool "${NODE_URL}/v${NODE24_VERSION}/${PACKAGERUNTIME}/node.lib" node24/bin else # Download external tools for Linux and OSX. @@ -258,6 +265,7 @@ else ARCH="darwin-arm64" acquireExternalTool "${NODE_URL}/v${NODE16_VERSION}/node-v${NODE16_VERSION}-${ARCH}.tar.gz" node16 fix_nested_dir acquireExternalTool "${NODE_URL}/v${NODE20_VERSION}/node-v${NODE20_VERSION}-${ARCH}.tar.gz" node20_1 fix_nested_dir + acquireExternalTool "${NODE_URL}/v${NODE24_VERSION}/node-v${NODE24_VERSION}-${ARCH}.tar.gz" node24 fix_nested_dir elif [[ "$PACKAGERUNTIME" == "linux-musl-arm64" ]]; then ARCH="linux-arm64-musl" @@ -267,6 +275,7 @@ else acquireExternalTool "${CONTAINER_URL}/nodejs/${ARCH}/node-v${NODE16_VERSION}-${ARCH}.tar.gz" node16/bin fix_nested_dir false node_alpine_arm64 acquireExternalTool "${CONTAINER_URL}/nodejs/${ARCH}/node-v${NODE20_VERSION}-${ARCH}.tar.gz" node20_1/bin fix_nested_dir false node_alpine_arm64 + acquireExternalTool "${CONTAINER_URL}/nodejs/${ARCH}/node-v${NODE24_VERSION}-${ARCH}.tar.gz" node24/bin fix_nested_dir false node_alpine_arm64 else case $PACKAGERUNTIME in "linux-musl-x64") ARCH="linux-x64-musl";; @@ -285,6 +294,7 @@ else fi acquireExternalTool "${NODE_URL}/v${NODE16_VERSION}/node-v${NODE16_VERSION}-${ARCH}.tar.gz" node16 fix_nested_dir acquireExternalTool "${NODE_URL}/v${NODE20_VERSION}/node-v${NODE20_VERSION}-${ARCH}.tar.gz" node20_1 fix_nested_dir + acquireExternalTool "${NODE_URL}/v${NODE24_VERSION}/node-v${NODE24_VERSION}-${ARCH}.tar.gz" node24 fix_nested_dir fi # remove `npm`, `npx`, `corepack`, and related `node_modules` from the `externals/node*` agent directory # they are installed along with node, but agent does not use them @@ -305,6 +315,11 @@ else rm "$LAYOUT_DIR/externals/node20_1/bin/npm" rm "$LAYOUT_DIR/externals/node20_1/bin/npx" rm "$LAYOUT_DIR/externals/node20_1/bin/corepack" + + rm -rf "$LAYOUT_DIR/externals/node24/lib" + rm "$LAYOUT_DIR/externals/node24/bin/npm" + rm "$LAYOUT_DIR/externals/node24/bin/npx" + rm "$LAYOUT_DIR/externals/node24/bin/corepack" fi if [[ "$L1_MODE" != "" || "$PRECACHE" != "" ]]; then From 0dd3a46a0851ea019ce86b09eb159602e8daa45d Mon Sep 17 00:00:00 2001 From: Surajit Shil Date: Fri, 17 Oct 2025 14:41:00 +0530 Subject: [PATCH 05/13] Suppressing maintainability error message --- src/Agent.Worker/ContainerOperationProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Agent.Worker/ContainerOperationProvider.cs b/src/Agent.Worker/ContainerOperationProvider.cs index debc04aacf..9c5cb87bcf 100644 --- a/src/Agent.Worker/ContainerOperationProvider.cs +++ b/src/Agent.Worker/ContainerOperationProvider.cs @@ -460,6 +460,7 @@ private async Task PullContainerAsync(IExecutionContext executionContext, Contai } } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Maintainability", "CA1505:Avoid unmaintainable code", Justification = "Complex container startup logic with multiple fallback paths")] private async Task StartContainerAsync(IExecutionContext executionContext, ContainerInfo container) { Trace.Entering(); From 2118bc940617d5f3fa7c95a390d91f92295a3d74 Mon Sep 17 00:00:00 2001 From: Surajit Shil Date: Fri, 17 Oct 2025 19:14:07 +0530 Subject: [PATCH 06/13] Adding parameter to GetNodeLocation in the test file --- src/Test/L0/NodeHandlerL0.cs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Test/L0/NodeHandlerL0.cs b/src/Test/L0/NodeHandlerL0.cs index 1116263ccf..8a794dc285 100644 --- a/src/Test/L0/NodeHandlerL0.cs +++ b/src/Test/L0/NodeHandlerL0.cs @@ -50,7 +50,7 @@ public void UseNodeForNodeHandlerEnvVarNotSet() nodeVersion = "node10"; // version 6 does not exist on Alpine } - string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, inContainer: false); + string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, node24ResultsInGlibCError: false, inContainer: false); string expectedLocation = Path.Combine(thc.GetDirectory(WellKnownDirectory.Externals), nodeVersion, "bin", @@ -88,7 +88,7 @@ public void UseNewNodeForNewNodeHandler(string nodeVersion) _ => throw new Exception("Invalid node version"), }; - string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, inContainer: false); + string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, node24ResultsInGlibCError: false, inContainer: false); string expectedLocation = Path.Combine(thc.GetDirectory(WellKnownDirectory.Externals), nodeVersion, "bin", @@ -117,7 +117,7 @@ public void UseNewNodeForNodeHandlerEnvVarSet() nodeHandler.ExecutionContext = CreateTestExecutionContext(thc); nodeHandler.Data = new Node10HandlerData(); - string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, inContainer: false); + string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, node24ResultsInGlibCError: false, inContainer: false); string expectedLocation = Path.Combine(thc.GetDirectory(WellKnownDirectory.Externals), "node10", "bin", @@ -151,7 +151,7 @@ public void UseNewNodeForNodeHandlerHostContextVarSet() nodeHandler.ExecutionContext = CreateTestExecutionContext(thc, variables); nodeHandler.Data = new Node10HandlerData(); - string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, inContainer: false); + string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, node24ResultsInGlibCError: false, inContainer: false); string expectedLocation = Path.Combine(thc.GetDirectory(WellKnownDirectory.Externals), "node10", "bin", @@ -183,7 +183,7 @@ public void UseNewNodeForNewNodeHandlerHostContextVarUnset() nodeHandler.ExecutionContext = CreateTestExecutionContext(thc, variables); nodeHandler.Data = new Node10HandlerData(); - string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, inContainer: false); + string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, node24ResultsInGlibCError: false, inContainer: false); string expectedLocation = Path.Combine(thc.GetDirectory(WellKnownDirectory.Externals), "node10", "bin", @@ -222,7 +222,7 @@ public void UseLTSNodeIfUseNodeKnobIsLTS() nodeHandler.ExecutionContext = CreateTestExecutionContext(thc, variables); nodeHandler.Data = new Node10HandlerData(); - string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, inContainer: false); + string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, node24ResultsInGlibCError: false, inContainer: false); string expectedLocation = Path.Combine(thc.GetDirectory(WellKnownDirectory.Externals), "node16", "bin", @@ -261,7 +261,7 @@ public void ThrowExceptionIfUseNodeKnobIsLTSAndLTSNotAvailable() nodeHandler.ExecutionContext = CreateTestExecutionContext(thc, variables); nodeHandler.Data = new Node10HandlerData(); - Assert.Throws(() => nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, inContainer: false)); + Assert.Throws(() => nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, node24ResultsInGlibCError: false, inContainer: false)); } } @@ -290,7 +290,7 @@ public void ThrowExceptionIfUseNodeKnobIsLTSAndFilteredPossibleNodeFoldersEmpty( nodeHandler.ExecutionContext = CreateTestExecutionContext(thc, variables); nodeHandler.Data = new Node10HandlerData(); - Assert.Throws(() => nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, inContainer: false)); + Assert.Throws(() => nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, node24ResultsInGlibCError: false, inContainer: false)); } } @@ -323,7 +323,7 @@ public void UseFirstAvailableNodeIfUseNodeKnobIsUpgrade() nodeHandler.ExecutionContext = CreateTestExecutionContext(thc, variables); nodeHandler.Data = new Node10HandlerData(); - string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, inContainer: false); + string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, node24ResultsInGlibCError: false, inContainer: false); string expectedLocation = Path.Combine(thc.GetDirectory(WellKnownDirectory.Externals), "nextAvailableNode1", "bin", @@ -362,7 +362,7 @@ public void UseSecondAvailableNodeIfUseNodeKnobIsUpgradeFilteredNodeFoldersFirst nodeHandler.ExecutionContext = CreateTestExecutionContext(thc, variables); nodeHandler.Data = new Node10HandlerData(); - string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, inContainer: false); + string actualLocation = nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, node24ResultsInGlibCError: false, inContainer: false); string expectedLocation = Path.Combine(thc.GetDirectory(WellKnownDirectory.Externals), "nextAvailableNode2", "bin", @@ -401,7 +401,7 @@ public void ThrowExceptionIfUseNodeKnobIsUpgradeFilteredNodeFoldersAllNotAvailab nodeHandler.ExecutionContext = CreateTestExecutionContext(thc, variables); nodeHandler.Data = new Node10HandlerData(); - Assert.Throws(() => nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, inContainer: false)); + Assert.Throws(() => nodeHandler.GetNodeLocation(node20ResultsInGlibCError: false, node24ResultsInGlibCError: false, inContainer: false)); } } From 92d0d75107249962bf0cf79d79cc6e4f797900b0 Mon Sep 17 00:00:00 2001 From: Surajit Shil Date: Fri, 17 Oct 2025 19:32:27 +0530 Subject: [PATCH 07/13] Code cleaning --- src/Agent.Worker/ContainerOperationProvider.cs | 4 +--- src/Agent.Worker/Handlers/NodeHandler.cs | 7 ------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/Agent.Worker/ContainerOperationProvider.cs b/src/Agent.Worker/ContainerOperationProvider.cs index 9c5cb87bcf..4e5260a707 100644 --- a/src/Agent.Worker/ContainerOperationProvider.cs +++ b/src/Agent.Worker/ContainerOperationProvider.cs @@ -957,7 +957,6 @@ string useDoubleQuotes(string value) bool useNode20InUnsupportedSystem = AgentKnobs.UseNode20InUnsupportedSystem.GetValue(executionContext).AsBoolean(); bool useNode24InUnsupportedSystem = AgentKnobs.UseNode24InUnsupportedSystem.GetValue(executionContext).AsBoolean(); // NEW knob - // NEW: Node24 glibc detection (BEFORE Node20) if (!useNode24InUnsupportedSystem) { var node24 = container.TranslateToContainerPath(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeHandler.Node24Folder, "bin", $"node{IOUtil.ExeExtension}")); @@ -999,10 +998,9 @@ string useDoubleQuotes(string value) } } - // Store the successful Node version path for later use if (!container.NeedsNode20Redirect) { - container.ResultNodePath = container.TranslateToContainerPath(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeHandler.Node24Folder, "bin", $"node{IOUtil.ExeExtension}")); // Node24 works + container.ResultNodePath = container.TranslateToContainerPath(Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeHandler.Node24Folder, "bin", $"node{IOUtil.ExeExtension}")); } else if (!container.NeedsNode16Redirect) { diff --git a/src/Agent.Worker/Handlers/NodeHandler.cs b/src/Agent.Worker/Handlers/NodeHandler.cs index ac36dcd3f5..40849b6efd 100644 --- a/src/Agent.Worker/Handlers/NodeHandler.cs +++ b/src/Agent.Worker/Handlers/NodeHandler.cs @@ -195,10 +195,8 @@ public async Task RunAsync() bool node20ResultsInGlibCErrorHost = false; bool node24ResultsInGlibCErrorHost = false; - // Separate glibc checks for each Node version if (PlatformUtil.HostOS == PlatformUtil.OS.Linux) { - // Node20 glibc check (controlled by its specific knob) if (!useNode20InUnsupportedSystem) { if (supportsNode20.HasValue) @@ -212,8 +210,6 @@ public async Task RunAsync() supportsNode20 = !node20ResultsInGlibCErrorHost; } } - - // Node24 glibc check (independent of Node20 knob) if (!useNode24InUnsupportedSystem) { if (supportsNode24.HasValue) @@ -273,7 +269,6 @@ public async Task RunAsync() } } } - try { @@ -522,8 +517,6 @@ private void Node16FallbackWarning(bool inContainer) } } - - // Add Node20 fallback warning method private void Node20FallbackWarning(bool inContainer) { if (inContainer) From 2a26dfdad54fd37590c12bc994c36865ba81eea1 Mon Sep 17 00:00:00 2001 From: Surajit Shil Date: Tue, 21 Oct 2025 13:41:43 +0530 Subject: [PATCH 08/13] Updating test files --- src/Test/L0/NodeHandlerL0.cs | 4 ++++ src/Test/L1/Worker/L1TestBase.cs | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/Test/L0/NodeHandlerL0.cs b/src/Test/L0/NodeHandlerL0.cs index 8a794dc285..0a96ef8ed9 100644 --- a/src/Test/L0/NodeHandlerL0.cs +++ b/src/Test/L0/NodeHandlerL0.cs @@ -64,6 +64,7 @@ public void UseNodeForNodeHandlerEnvVarNotSet() [InlineData("node10")] [InlineData("node16")] [InlineData("node20_1")] + [InlineData("node24")] [Trait("Level", "L0")] [Trait("Category", "Common")] public void UseNewNodeForNewNodeHandler(string nodeVersion) @@ -85,6 +86,7 @@ public void UseNewNodeForNewNodeHandler(string nodeVersion) "node10" => new Node10HandlerData(), "node16" => new Node16HandlerData(), "node20_1" => new Node20_1HandlerData(), + "node24" => new Node24HandlerData(), _ => throw new Exception("Invalid node version"), }; @@ -471,6 +473,8 @@ private void ResetNodeKnobs() Environment.SetEnvironmentVariable("AGENT_USE_NODE10", null); Environment.SetEnvironmentVariable("AGENT_USE_NODE20_1", null); Environment.SetEnvironmentVariable("AGENT_USE_NODE20_IN_UNSUPPORTED_SYSTEM", null); + Environment.SetEnvironmentVariable("AGENT_USE_NODE24", null); + Environment.SetEnvironmentVariable("AGENT_USE_NODE24_IN_UNSUPPORTED_SYSTEM", null); } } } \ No newline at end of file diff --git a/src/Test/L1/Worker/L1TestBase.cs b/src/Test/L1/Worker/L1TestBase.cs index 16138739f6..16886fd7d4 100644 --- a/src/Test/L1/Worker/L1TestBase.cs +++ b/src/Test/L1/Worker/L1TestBase.cs @@ -370,6 +370,8 @@ private void ResetNodeKnobs() Environment.SetEnvironmentVariable("AGENT_USE_NODE10", null); Environment.SetEnvironmentVariable("AGENT_USE_NODE20_1", null); Environment.SetEnvironmentVariable("AGENT_USE_NODE20_IN_UNSUPPORTED_SYSTEM", null); + Environment.SetEnvironmentVariable("AGENT_USE_NODE24", null); + Environment.SetEnvironmentVariable("AGENT_USE_NODE24_IN_UNSUPPORTED_SYSTEM", null); } protected virtual void Dispose(bool disposing) From 7f75e46203dfcffbc78564211e6b2733d309de8a Mon Sep 17 00:00:00 2001 From: Surajit Shil Date: Tue, 21 Oct 2025 14:02:59 +0530 Subject: [PATCH 09/13] Minor fixes --- src/Agent.Worker/ContainerOperationProvider.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Agent.Worker/ContainerOperationProvider.cs b/src/Agent.Worker/ContainerOperationProvider.cs index 4e5260a707..ac712d2c95 100644 --- a/src/Agent.Worker/ContainerOperationProvider.cs +++ b/src/Agent.Worker/ContainerOperationProvider.cs @@ -639,7 +639,7 @@ string useDoubleQuotes(string value) foreach (string logLine in containerLogs) { - if (logLine.Contains(labelContainerStartupUsingNode24)) // NEW + if (logLine.Contains(labelContainerStartupUsingNode24)) { executionContext.Debug("Using Node 24 for container startup."); containerStartupCompleted = true; @@ -955,7 +955,7 @@ string useDoubleQuotes(string value) if (PlatformUtil.RunningOnLinux) { bool useNode20InUnsupportedSystem = AgentKnobs.UseNode20InUnsupportedSystem.GetValue(executionContext).AsBoolean(); - bool useNode24InUnsupportedSystem = AgentKnobs.UseNode24InUnsupportedSystem.GetValue(executionContext).AsBoolean(); // NEW knob + bool useNode24InUnsupportedSystem = AgentKnobs.UseNode24InUnsupportedSystem.GetValue(executionContext).AsBoolean(); if (!useNode24InUnsupportedSystem) { From fb10e7d62a4419c453949ba677acffa77e9b5244 Mon Sep 17 00:00:00 2001 From: Surajit Shil Date: Thu, 23 Oct 2025 10:02:06 +0530 Subject: [PATCH 10/13] Adding conditional download of node24 for win-x86 systems --- src/Misc/externals.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Misc/externals.sh b/src/Misc/externals.sh index 1777e2c313..12b1906bb9 100644 --- a/src/Misc/externals.sh +++ b/src/Misc/externals.sh @@ -6,6 +6,7 @@ L1_MODE=$4 INCLUDE_NODE6=${INCLUDE_NODE6:-true} INCLUDE_NODE10=${INCLUDE_NODE10:-true} +INCLUDE_NODE24=${INCLUDE_NODE24:-true} CONTAINER_URL=https://vstsagenttools.blob.core.windows.net/tools @@ -180,6 +181,9 @@ if [[ "$PACKAGERUNTIME" == "win-x"* ]]; then # Copy vstsom to vstshost for default PowerShell handler behavior cp -r "$LAYOUT_DIR/externals/vstsom/"* "$LAYOUT_DIR/externals/vstshost/" fi + if [[ "$PACKAGERUNTIME" == "win-x86" ]]; then + INCLUDE_NODE24=false + fi acquireExternalTool "$CONTAINER_URL/mingit/${MINGIT_VERSION}/MinGit-${MINGIT_VERSION}-${BIT}-bit.zip" git acquireExternalTool "$CONTAINER_URL/git-lfs/${LFS_VERSION}/x${BIT}/git-lfs.exe" "git/mingw${BIT}/bin" @@ -202,8 +206,10 @@ if [[ "$PACKAGERUNTIME" == "win-x"* ]]; then acquireExternalTool "${NODE_URL}/v${NODE16_VERSION}/${PACKAGERUNTIME}/node.lib" node16/bin acquireExternalTool "${NODE_URL}/v${NODE20_VERSION}/${PACKAGERUNTIME}/node.exe" node20_1/bin acquireExternalTool "${NODE_URL}/v${NODE20_VERSION}/${PACKAGERUNTIME}/node.lib" node20_1/bin + if [[ "$INCLUDE_NODE24" == "true" ]]; then acquireExternalTool "${NODE_URL}/v${NODE24_VERSION}/${PACKAGERUNTIME}/node.exe" node24/bin acquireExternalTool "${NODE_URL}/v${NODE24_VERSION}/${PACKAGERUNTIME}/node.lib" node24/bin + fi elif [[ "$PACKAGERUNTIME" == "win-arm64" || "$PACKAGERUNTIME" == "win-arm32" ]]; then # Download external tools for Windows ARM From 23065ad7d555cf6478ddc01934d469d54bb9a532 Mon Sep 17 00:00:00 2001 From: Surajit Shil Date: Fri, 24 Oct 2025 13:05:17 +0530 Subject: [PATCH 11/13] New PR with the fixes --- src/Agent.Sdk/Knob/AgentKnobs.cs | 4 +- src/Agent.Worker/Handlers/NodeHandler.cs | 149 ++++++++--------------- src/Agent.Worker/TaskManager.cs | 20 +-- 3 files changed, 63 insertions(+), 110 deletions(-) diff --git a/src/Agent.Sdk/Knob/AgentKnobs.cs b/src/Agent.Sdk/Knob/AgentKnobs.cs index 7c95543d3c..2d237080a0 100644 --- a/src/Agent.Sdk/Knob/AgentKnobs.cs +++ b/src/Agent.Sdk/Knob/AgentKnobs.cs @@ -203,12 +203,14 @@ public class AgentKnobs public static readonly Knob UseNode24 = new Knob( nameof(UseNode24), "Forces the agent to use Node 24 handler for all Node-based tasks", + new PipelineFeatureSource("UseNode24"), new RuntimeKnobSource("AGENT_USE_NODE24"), new EnvironmentKnobSource("AGENT_USE_NODE24"), new BuiltInDefaultKnobSource("false")); public static readonly Knob UseNode24InUnsupportedSystem = new Knob( nameof(UseNode24InUnsupportedSystem), "Forces the agent to use Node 24 handler for all Node-based tasks, even if it's in an unsupported system", + new PipelineFeatureSource("UseNode24InUnsupportedSystem"), new RuntimeKnobSource("AGENT_USE_NODE24_IN_UNSUPPORTED_SYSTEM"), new EnvironmentKnobSource("AGENT_USE_NODE24_IN_UNSUPPORTED_SYSTEM"), new BuiltInDefaultKnobSource("false")); @@ -720,8 +722,8 @@ public class AgentKnobs public static readonly Knob UseNode20ToStartContainer = new Knob( nameof(UseNode20ToStartContainer), "If true, the agent will use Node 20 to start docker container when executing container job and the container platform is the same as the host platform.", - new RuntimeKnobSource("AZP_AGENT_USE_NODE20_TO_START_CONTAINER"), new PipelineFeatureSource("UseNode20ToStartContainer"), + new RuntimeKnobSource("AZP_AGENT_USE_NODE20_TO_START_CONTAINER"), new BuiltInDefaultKnobSource("false")); public static readonly Knob UseNode24ToStartContainer = new Knob( nameof(UseNode24ToStartContainer), diff --git a/src/Agent.Worker/Handlers/NodeHandler.cs b/src/Agent.Worker/Handlers/NodeHandler.cs index 40849b6efd..c176cab8fa 100644 --- a/src/Agent.Worker/Handlers/NodeHandler.cs +++ b/src/Agent.Worker/Handlers/NodeHandler.cs @@ -205,7 +205,7 @@ public async Task RunAsync() } else { - node20ResultsInGlibCErrorHost = await CheckIfNode20ResultsInGlibCError(); + node20ResultsInGlibCErrorHost = await CheckIfNodeResultsInGlibCError(NodeHandler.Node20_1Folder); ExecutionContext.EmitHostNode20FallbackTelemetry(node20ResultsInGlibCErrorHost); supportsNode20 = !node20ResultsInGlibCErrorHost; } @@ -218,8 +218,8 @@ public async Task RunAsync() } else { - node24ResultsInGlibCErrorHost = await CheckIfNode24ResultsInGlibCError(); - ExecutionContext.EmitHostNode24FallbackTelemetry(node24ResultsInGlibCErrorHost); // Add this method + node24ResultsInGlibCErrorHost = await CheckIfNodeResultsInGlibCError(NodeHandler.Node24Folder); + ExecutionContext.EmitHostNode24FallbackTelemetry(node24ResultsInGlibCErrorHost); supportsNode24 = !node24ResultsInGlibCErrorHost; } } @@ -317,23 +317,50 @@ public async Task RunAsync() } } - private async Task CheckIfNode20ResultsInGlibCError() + private async Task CheckIfNodeResultsInGlibCError(string nodeFolder) { - var node20 = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeHandler.Node20_1Folder, "bin", $"node{IOUtil.ExeExtension}"); - List nodeVersionOutput = await ExecuteCommandAsync(ExecutionContext, node20, "-v", requireZeroExitCode: false, showOutputOnFailureOnly: true); - var node20ResultsInGlibCError = WorkerUtilities.IsCommandResultGlibcError(ExecutionContext, nodeVersionOutput, out string nodeInfoLine); + var nodePath = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), nodeFolder, "bin", $"node{IOUtil.ExeExtension}"); + List nodeVersionOutput = await ExecuteCommandAsync(ExecutionContext, nodePath, "-v", requireZeroExitCode: false, showOutputOnFailureOnly: true); + var nodeResultsInGlibCError = WorkerUtilities.IsCommandResultGlibcError(ExecutionContext, nodeVersionOutput, out string nodeInfoLine); - return node20ResultsInGlibCError; + return nodeResultsInGlibCError; } - private async Task CheckIfNode24ResultsInGlibCError() + + private string GetNodeFolderWithFallback(string preferredNodeFolder, bool node20ResultsInGlibCError, bool node24ResultsInGlibCError, bool inContainer) { - var node24 = Path.Combine(HostContext.GetDirectory(WellKnownDirectory.Externals), NodeHandler.Node24Folder, "bin", $"node{IOUtil.ExeExtension}"); - List nodeVersionOutput = await ExecuteCommandAsync(ExecutionContext, node24, "-v", requireZeroExitCode: false, showOutputOnFailureOnly: true); - var node24ResultsInGlibCError = WorkerUtilities.IsCommandResultGlibcError(ExecutionContext, nodeVersionOutput, out string nodeInfoLine); + switch (preferredNodeFolder) + { + case var folder when folder == NodeHandler.Node24Folder: + if (node24ResultsInGlibCError) + { + // Fallback to Node20, then Node16 if Node20 also fails + if (node20ResultsInGlibCError) + { + NodeFallbackWarning("20", "16", inContainer); + return NodeHandler.Node16Folder; + } + else + { + NodeFallbackWarning("24", "20", inContainer); + return NodeHandler.Node20_1Folder; + } + } + return NodeHandler.Node24Folder; + + case var folder when folder == NodeHandler.Node20_1Folder: + if (node20ResultsInGlibCError) + { + NodeFallbackWarning("20", "16", inContainer); + return NodeHandler.Node16Folder; + } + return NodeHandler.Node20_1Folder; - return node24ResultsInGlibCError; + default: + return preferredNodeFolder; + } } + public string GetNodeLocation(bool node20ResultsInGlibCError, bool node24ResultsInGlibCError, bool inContainer) { bool useNode10 = AgentKnobs.UseNode10.GetValue(ExecutionContext).AsBoolean(); @@ -350,39 +377,12 @@ public string GetNodeLocation(bool node20ResultsInGlibCError, bool node24Results if (taskHasNode24Data) { Trace.Info($"Task.json has node24 handler data: {taskHasNode24Data}"); - - if (node24ResultsInGlibCError) - { - // Fallback to Node20, then Node16 if Node20 also fails - if (node20ResultsInGlibCError) - { - nodeFolder = NodeHandler.Node16Folder; - Node16FallbackWarning(inContainer); - } - else - { - nodeFolder = NodeHandler.Node20_1Folder; - Node20FallbackWarning(inContainer); - } - } - else - { - nodeFolder = NodeHandler.Node24Folder; - } + nodeFolder = GetNodeFolderWithFallback(NodeHandler.Node24Folder, node20ResultsInGlibCError, node24ResultsInGlibCError, inContainer); } else if (taskHasNode20_1Data) { Trace.Info($"Task.json has node20_1 handler data: {taskHasNode20_1Data} node20ResultsInGlibCError = {node20ResultsInGlibCError}"); - - if (node20ResultsInGlibCError) - { - nodeFolder = NodeHandler.Node16Folder; - Node16FallbackWarning(inContainer); - } - else - { - nodeFolder = NodeHandler.Node20_1Folder; - } + nodeFolder = GetNodeFolderWithFallback(NodeHandler.Node20_1Folder, node20ResultsInGlibCError, node24ResultsInGlibCError, inContainer); } else if (taskHasNode16Data) { @@ -403,42 +403,15 @@ public string GetNodeLocation(bool node20ResultsInGlibCError, bool node24Results if (useNode24) { Trace.Info($"Found UseNode24 knob, using node24 for node tasks: {useNode24}"); - - if (node24ResultsInGlibCError) - { - // Fallback to Node20, then Node16 if Node20 also fails - if (node20ResultsInGlibCError) - { - nodeFolder = NodeHandler.Node16Folder; - Node16FallbackWarning(inContainer); - } - else - { - nodeFolder = NodeHandler.Node20_1Folder; - Node20FallbackWarning(inContainer); - } - } - else - { - nodeFolder = NodeHandler.Node24Folder; - } + nodeFolder = GetNodeFolderWithFallback(NodeHandler.Node24Folder, node20ResultsInGlibCError, node24ResultsInGlibCError, inContainer); } - if (useNode20_1) + else if (useNode20_1) { Trace.Info($"Found UseNode20_1 knob, using node20_1 for node tasks {useNode20_1} node20ResultsInGlibCError = {node20ResultsInGlibCError}"); - - if (node20ResultsInGlibCError) - { - nodeFolder = NodeHandler.Node16Folder; - Node16FallbackWarning(inContainer); - } - else - { - nodeFolder = NodeHandler.Node20_1Folder; - } + nodeFolder = GetNodeFolderWithFallback(NodeHandler.Node20_1Folder, node20ResultsInGlibCError, node24ResultsInGlibCError, inContainer); } - if (useNode10) + else if (useNode10) { Trace.Info($"Found UseNode10 knob, use node10 for node tasks: {useNode10}"); nodeFolder = NodeHandler.node10Folder; @@ -501,34 +474,12 @@ public string GetNodeLocation(bool node20ResultsInGlibCError, bool node24Results return nodeHandlerHelper.GetNodeFolderPath(nodeFolder, HostContext); } - private void Node16FallbackWarning(bool inContainer) + private void NodeFallbackWarning(string fromVersion, string toVersion, bool inContainer) { - if (inContainer) - { - ExecutionContext.Warning($"The container operating system doesn't support Node20. Using Node16 instead. " + - "Please upgrade the operating system of the container to remain compatible with future updates of tasks: " + - "https://github.com/nodesource/distributions"); - } - else - { - ExecutionContext.Warning($"The agent operating system doesn't support Node20. Using Node16 instead. " + - "Please upgrade the operating system of the agent to remain compatible with future updates of tasks: " + + string systemType = inContainer ? "container" : "agent"; + ExecutionContext.Warning($"The {systemType} operating system doesn't support Node{fromVersion}. Using Node{toVersion} instead. " + + $"Please upgrade the operating system of the {systemType} to remain compatible with future updates of tasks: " + "https://github.com/nodesource/distributions"); - } - } - - private void Node20FallbackWarning(bool inContainer) - { - if (inContainer) - { - ExecutionContext.Warning($"The container operating system doesn't support Node24. Using Node20 instead. " + - "Please upgrade the operating system of the container to remain compatible with future updates of tasks."); - } - else - { - ExecutionContext.Warning($"The agent operating system doesn't support Node24. Using Node20 instead. " + - "Please upgrade the operating system of the agent to remain compatible with future updates of tasks."); - } } diff --git a/src/Agent.Worker/TaskManager.cs b/src/Agent.Worker/TaskManager.cs index 6e6e379bff..b5c33e7134 100644 --- a/src/Agent.Worker/TaskManager.cs +++ b/src/Agent.Worker/TaskManager.cs @@ -887,29 +887,29 @@ public string WorkingDirectory public sealed class NodeHandlerData : BaseNodeHandlerData { - public override int Priority => 5; + public override int Priority => 105; } public sealed class Node10HandlerData : BaseNodeHandlerData { - public override int Priority => 4; + public override int Priority => 104; } public sealed class Node16HandlerData : BaseNodeHandlerData { - public override int Priority => 3; + public override int Priority => 103; } public sealed class Node20_1HandlerData : BaseNodeHandlerData { - public override int Priority => 2; + public override int Priority => 102; } public sealed class Node24HandlerData : BaseNodeHandlerData { - public override int Priority => 1; + public override int Priority => 101; } public sealed class PowerShell3HandlerData : HandlerData { - public override int Priority => 6; + public override int Priority => 106; } public sealed class PowerShellHandlerData : HandlerData @@ -927,7 +927,7 @@ public string ArgumentFormat } } - public override int Priority => 7; + public override int Priority => 107; public string WorkingDirectory { @@ -958,7 +958,7 @@ public string ArgumentFormat } } - public override int Priority => 8; + public override int Priority => 108; public string WorkingDirectory { @@ -1015,7 +1015,7 @@ public string InlineScript } } - public override int Priority => 8; + public override int Priority => 108; public string ScriptType { @@ -1072,7 +1072,7 @@ public string ModifyEnvironment } } - public override int Priority => 9; + public override int Priority => 109; public string WorkingDirectory { From 152087525287d2e0d55fbaa025e5322a547891f8 Mon Sep 17 00:00:00 2001 From: Surajit Shil Date: Fri, 24 Oct 2025 13:09:15 +0530 Subject: [PATCH 12/13] Fixing spaces --- src/Agent.Sdk/Knob/AgentKnobs.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Agent.Sdk/Knob/AgentKnobs.cs b/src/Agent.Sdk/Knob/AgentKnobs.cs index 2d237080a0..0aefb3db42 100644 --- a/src/Agent.Sdk/Knob/AgentKnobs.cs +++ b/src/Agent.Sdk/Knob/AgentKnobs.cs @@ -725,6 +725,7 @@ public class AgentKnobs new PipelineFeatureSource("UseNode20ToStartContainer"), new RuntimeKnobSource("AZP_AGENT_USE_NODE20_TO_START_CONTAINER"), new BuiltInDefaultKnobSource("false")); + public static readonly Knob UseNode24ToStartContainer = new Knob( nameof(UseNode24ToStartContainer), "If true, try to start container job using Node24, then fallback to Node20, then Node16.", From 9aa7b9e526d6a389c74c7961cccdf01af573b566 Mon Sep 17 00:00:00 2001 From: Surajit Shil Date: Fri, 24 Oct 2025 13:12:12 +0530 Subject: [PATCH 13/13] Fixing spaces --- src/Agent.Sdk/Knob/AgentKnobs.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Agent.Sdk/Knob/AgentKnobs.cs b/src/Agent.Sdk/Knob/AgentKnobs.cs index 0aefb3db42..806ade56dd 100644 --- a/src/Agent.Sdk/Knob/AgentKnobs.cs +++ b/src/Agent.Sdk/Knob/AgentKnobs.cs @@ -207,6 +207,7 @@ public class AgentKnobs new RuntimeKnobSource("AGENT_USE_NODE24"), new EnvironmentKnobSource("AGENT_USE_NODE24"), new BuiltInDefaultKnobSource("false")); + public static readonly Knob UseNode24InUnsupportedSystem = new Knob( nameof(UseNode24InUnsupportedSystem), "Forces the agent to use Node 24 handler for all Node-based tasks, even if it's in an unsupported system",