From bcde4777ef2c45253b30a785b2d50c7761caa1f9 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Wed, 11 Feb 2026 13:39:16 +0000 Subject: [PATCH 1/7] Add Node.js 20 deprecation warning annotation When the actions.runner.warnonnode20 feature flag is enabled, the runner collects all actions using Node.js 20 (including node12/16 that get migrated to node20) during the job and emits a single warning annotation at job finalization: "Node.js 20 actions are deprecated and will stop working on June 2nd, 2025. Please update the following actions to use Node.js 24: {actions}. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/" Also adds the blog post link to the Phase 2 (node24 default) info message. --- src/Runner.Common/Constants.cs | 4 + src/Runner.Worker/ExecutionContext.cs | 3 + src/Runner.Worker/GlobalContext.cs | 1 + src/Runner.Worker/Handlers/HandlerFactory.cs | 30 ++- src/Runner.Worker/JobExtension.cs | 8 + src/Test/L0/Worker/HandlerFactoryL0.cs | 203 ++++++++++++++++++- 6 files changed, 247 insertions(+), 2 deletions(-) diff --git a/src/Runner.Common/Constants.cs b/src/Runner.Common/Constants.cs index f2d5dd26ee5..3cc9d28b400 100644 --- a/src/Runner.Common/Constants.cs +++ b/src/Runner.Common/Constants.cs @@ -190,6 +190,10 @@ public static class NodeMigration // Feature flags for controlling the migration phases public static readonly string UseNode24ByDefaultFlag = "actions.runner.usenode24bydefault"; public static readonly string RequireNode24Flag = "actions.runner.requirenode24"; + public static readonly string WarnOnNode20Flag = "actions.runner.warnonnode20"; + + // Blog post URL for Node 20 deprecation + public static readonly string Node20DeprecationUrl = "https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/"; } public static readonly string InternalTelemetryIssueDataKey = "_internal_telemetry"; diff --git a/src/Runner.Worker/ExecutionContext.cs b/src/Runner.Worker/ExecutionContext.cs index 2a7cd11fb06..53484e6b603 100644 --- a/src/Runner.Worker/ExecutionContext.cs +++ b/src/Runner.Worker/ExecutionContext.cs @@ -856,6 +856,9 @@ public void InitializeJob(Pipelines.AgentJobRequestMessage message, Cancellation // Job level annotations Global.JobAnnotations = new List(); + // Track Node.js 20 actions for deprecation warning + Global.DeprecatedNode20Actions = new HashSet(StringComparer.OrdinalIgnoreCase); + // Job Outputs JobOutputs = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/src/Runner.Worker/GlobalContext.cs b/src/Runner.Worker/GlobalContext.cs index 6d4494843c2..27c326d68f9 100644 --- a/src/Runner.Worker/GlobalContext.cs +++ b/src/Runner.Worker/GlobalContext.cs @@ -33,5 +33,6 @@ public sealed class GlobalContext public bool HasActionManifestMismatch { get; set; } public bool HasDeprecatedSetOutput { get; set; } public bool HasDeprecatedSaveState { get; set; } + public HashSet DeprecatedNode20Actions { get; set; } } } diff --git a/src/Runner.Worker/Handlers/HandlerFactory.cs b/src/Runner.Worker/Handlers/HandlerFactory.cs index ee022ec9d12..41580103f7f 100644 --- a/src/Runner.Worker/Handlers/HandlerFactory.cs +++ b/src/Runner.Worker/Handlers/HandlerFactory.cs @@ -65,6 +65,20 @@ public IHandler Create( nodeData.NodeVersion = Common.Constants.Runner.NodeMigration.Node20; } + // Track Node.js 20 actions for deprecation annotation + if (string.Equals(nodeData.NodeVersion, Constants.Runner.NodeMigration.Node20, StringComparison.InvariantCultureIgnoreCase)) + { + bool warnOnNode20 = executionContext.Global.Variables?.GetBoolean(Constants.Runner.NodeMigration.WarnOnNode20Flag) ?? false; + if (warnOnNode20) + { + string actionName = GetActionName(action); + if (!string.IsNullOrEmpty(actionName)) + { + executionContext.Global.DeprecatedNode20Actions?.Add(actionName); + } + } + } + // Check if node20 was explicitly specified in the action // We don't modify if node24 was explicitly specified if (string.Equals(nodeData.NodeVersion, Constants.Runner.NodeMigration.Node20, StringComparison.InvariantCultureIgnoreCase)) @@ -90,7 +104,8 @@ public IHandler Create( if (useNode24ByDefault && !requireNode24 && string.Equals(finalNodeVersion, Constants.Runner.NodeMigration.Node24, StringComparison.OrdinalIgnoreCase)) { string infoMessage = "Node 20 is being deprecated. This workflow is running with Node 24 by default. " + - "If you need to temporarily use Node 20, you can set the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true environment variable."; + "If you need to temporarily use Node 20, you can set the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true environment variable. " + + $"For more information see: {Constants.Runner.NodeMigration.Node20DeprecationUrl}"; executionContext.Output(infoMessage); } } @@ -129,5 +144,18 @@ public IHandler Create( handler.LocalActionContainerSetupSteps = localActionContainerSetupSteps; return handler; } + + private static string GetActionName(Pipelines.ActionStepDefinitionReference action) + { + if (action is Pipelines.RepositoryPathReference repoRef) + { + var pathString = string.IsNullOrEmpty(repoRef.Path) ? string.Empty : $"/{repoRef.Path}"; + return string.IsNullOrEmpty(repoRef.Ref) + ? $"{repoRef.Name}{pathString}" + : $"{repoRef.Name}{pathString}@{repoRef.Ref}"; + } + + return null; + } } } diff --git a/src/Runner.Worker/JobExtension.cs b/src/Runner.Worker/JobExtension.cs index ea36034ecb0..8f4585955c2 100644 --- a/src/Runner.Worker/JobExtension.cs +++ b/src/Runner.Worker/JobExtension.cs @@ -735,6 +735,14 @@ public async Task FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRe context.Global.JobTelemetry.Add(new JobTelemetry() { Type = JobTelemetryType.ConnectivityCheck, Message = $"Fail to check service connectivity. {ex.Message}" }); } } + + // Add deprecation warning annotation for Node.js 20 actions + if (context.Global.DeprecatedNode20Actions?.Count > 0) + { + var actionsList = string.Join(", ", context.Global.DeprecatedNode20Actions); + var deprecationMessage = $"Node.js 20 actions are deprecated and will stop working on June 2nd, 2025. Please update the following actions to use Node.js 24: {actionsList}. For more information see: {Constants.Runner.NodeMigration.Node20DeprecationUrl}"; + context.Warning(deprecationMessage); + } } catch (Exception ex) { diff --git a/src/Test/L0/Worker/HandlerFactoryL0.cs b/src/Test/L0/Worker/HandlerFactoryL0.cs index 37981e46aa9..4cfb70bc5dd 100644 --- a/src/Test/L0/Worker/HandlerFactoryL0.cs +++ b/src/Test/L0/Worker/HandlerFactoryL0.cs @@ -74,7 +74,7 @@ public void IsNodeVersionUpgraded(string inputVersion, string expectedVersion) } } - + [Fact] [Trait("Level", "L0")] @@ -116,5 +116,206 @@ public void Node24ExplicitlyRequested_HonoredByDefault() Assert.Equal("node24", handler.Data.NodeVersion); } } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void Node20Action_TrackedWhenWarnFlagEnabled() + { + using (TestHostContext hc = CreateTestContext()) + { + // Arrange. + var hf = new HandlerFactory(); + hf.Initialize(hc); + + var variables = new Dictionary + { + { Constants.Runner.NodeMigration.WarnOnNode20Flag, new VariableValue("true") } + }; + Variables serverVariables = new(hc, variables); + var deprecatedActions = new HashSet(StringComparer.OrdinalIgnoreCase); + + _ec.Setup(x => x.Global).Returns(new GlobalContext() + { + Variables = serverVariables, + EnvironmentVariables = new Dictionary(), + DeprecatedNode20Actions = deprecatedActions + }); + + var actionRef = new RepositoryPathReference + { + Name = "actions/checkout", + Ref = "v4" + }; + + // Act. + var data = new NodeJSActionExecutionData(); + data.NodeVersion = "node20"; + hf.Create( + _ec.Object, + actionRef, + new Mock().Object, + data, + new Dictionary(), + new Dictionary(), + new Variables(hc, new Dictionary()), + "", + new List() + ); + + // Assert. + Assert.Contains("actions/checkout@v4", deprecatedActions); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void Node20Action_NotTrackedWhenWarnFlagDisabled() + { + using (TestHostContext hc = CreateTestContext()) + { + // Arrange. + var hf = new HandlerFactory(); + hf.Initialize(hc); + + var variables = new Dictionary(); + Variables serverVariables = new(hc, variables); + var deprecatedActions = new HashSet(StringComparer.OrdinalIgnoreCase); + + _ec.Setup(x => x.Global).Returns(new GlobalContext() + { + Variables = serverVariables, + EnvironmentVariables = new Dictionary(), + DeprecatedNode20Actions = deprecatedActions + }); + + var actionRef = new RepositoryPathReference + { + Name = "actions/checkout", + Ref = "v4" + }; + + // Act. + var data = new NodeJSActionExecutionData(); + data.NodeVersion = "node20"; + hf.Create( + _ec.Object, + actionRef, + new Mock().Object, + data, + new Dictionary(), + new Dictionary(), + new Variables(hc, new Dictionary()), + "", + new List() + ); + + // Assert - should not track when flag is disabled + Assert.Empty(deprecatedActions); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void Node24Action_NotTrackedEvenWhenWarnFlagEnabled() + { + using (TestHostContext hc = CreateTestContext()) + { + // Arrange. + var hf = new HandlerFactory(); + hf.Initialize(hc); + + var variables = new Dictionary + { + { Constants.Runner.NodeMigration.WarnOnNode20Flag, new VariableValue("true") } + }; + Variables serverVariables = new(hc, variables); + var deprecatedActions = new HashSet(StringComparer.OrdinalIgnoreCase); + + _ec.Setup(x => x.Global).Returns(new GlobalContext() + { + Variables = serverVariables, + EnvironmentVariables = new Dictionary(), + DeprecatedNode20Actions = deprecatedActions + }); + + var actionRef = new RepositoryPathReference + { + Name = "actions/checkout", + Ref = "v5" + }; + + // Act. + var data = new NodeJSActionExecutionData(); + data.NodeVersion = "node24"; + hf.Create( + _ec.Object, + actionRef, + new Mock().Object, + data, + new Dictionary(), + new Dictionary(), + new Variables(hc, new Dictionary()), + "", + new List() + ); + + // Assert - node24 actions should not be tracked + Assert.Empty(deprecatedActions); + } + } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void Node12Action_TrackedAsDeprecatedWhenWarnFlagEnabled() + { + using (TestHostContext hc = CreateTestContext()) + { + // Arrange. + var hf = new HandlerFactory(); + hf.Initialize(hc); + + var variables = new Dictionary + { + { Constants.Runner.NodeMigration.WarnOnNode20Flag, new VariableValue("true") } + }; + Variables serverVariables = new(hc, variables); + var deprecatedActions = new HashSet(StringComparer.OrdinalIgnoreCase); + + _ec.Setup(x => x.Global).Returns(new GlobalContext() + { + Variables = serverVariables, + EnvironmentVariables = new Dictionary(), + DeprecatedNode20Actions = deprecatedActions + }); + + var actionRef = new RepositoryPathReference + { + Name = "some-org/old-action", + Ref = "v1" + }; + + // Act - node12 gets migrated to node20, then should be tracked + var data = new NodeJSActionExecutionData(); + data.NodeVersion = "node12"; + hf.Create( + _ec.Object, + actionRef, + new Mock().Object, + data, + new Dictionary(), + new Dictionary(), + new Variables(hc, new Dictionary()), + "", + new List() + ); + + // Assert - node12 gets migrated to node20 and should be tracked + Assert.Contains("some-org/old-action@v1", deprecatedActions); + } + } } } From 0bf26e18f02aa44ed228e61b562a52bbc56b0960 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Wed, 11 Feb 2026 13:42:29 +0000 Subject: [PATCH 2/7] Fix deprecation message wording: actions will be forced to node24, not stop working --- src/Runner.Worker/JobExtension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Runner.Worker/JobExtension.cs b/src/Runner.Worker/JobExtension.cs index 8f4585955c2..3ad81e77742 100644 --- a/src/Runner.Worker/JobExtension.cs +++ b/src/Runner.Worker/JobExtension.cs @@ -740,7 +740,7 @@ public async Task FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRe if (context.Global.DeprecatedNode20Actions?.Count > 0) { var actionsList = string.Join(", ", context.Global.DeprecatedNode20Actions); - var deprecationMessage = $"Node.js 20 actions are deprecated and will stop working on June 2nd, 2025. Please update the following actions to use Node.js 24: {actionsList}. For more information see: {Constants.Runner.NodeMigration.Node20DeprecationUrl}"; + var deprecationMessage = $"Node.js 20 actions are deprecated. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2025. Please update the following actions to use Node.js 24: {actionsList}. For more information see: {Constants.Runner.NodeMigration.Node20DeprecationUrl}"; context.Warning(deprecationMessage); } } From d6566f6418780e5361dadf04ffc951f5934a7807 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Wed, 11 Feb 2026 13:43:46 +0000 Subject: [PATCH 3/7] Improve deprecation message: list actions running on node20, suggest checking for updates --- src/Runner.Worker/JobExtension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Runner.Worker/JobExtension.cs b/src/Runner.Worker/JobExtension.cs index 3ad81e77742..2adf8b42e46 100644 --- a/src/Runner.Worker/JobExtension.cs +++ b/src/Runner.Worker/JobExtension.cs @@ -740,7 +740,7 @@ public async Task FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRe if (context.Global.DeprecatedNode20Actions?.Count > 0) { var actionsList = string.Join(", ", context.Global.DeprecatedNode20Actions); - var deprecationMessage = $"Node.js 20 actions are deprecated. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2025. Please update the following actions to use Node.js 24: {actionsList}. For more information see: {Constants.Runner.NodeMigration.Node20DeprecationUrl}"; + var deprecationMessage = $"Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: {actionsList}. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2025. Please check if updated versions of these actions are available that support Node.js 24. For more information see: {Constants.Runner.NodeMigration.Node20DeprecationUrl}"; context.Warning(deprecationMessage); } } From ec385f600c4e40ff948b7870d788b5265782b733 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Wed, 11 Feb 2026 13:45:51 +0000 Subject: [PATCH 4/7] Add opt-in/opt-out environment variable guidance to deprecation warning --- src/Runner.Worker/JobExtension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Runner.Worker/JobExtension.cs b/src/Runner.Worker/JobExtension.cs index 2adf8b42e46..9168968cf0a 100644 --- a/src/Runner.Worker/JobExtension.cs +++ b/src/Runner.Worker/JobExtension.cs @@ -740,7 +740,7 @@ public async Task FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRe if (context.Global.DeprecatedNode20Actions?.Count > 0) { var actionsList = string.Join(", ", context.Global.DeprecatedNode20Actions); - var deprecationMessage = $"Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: {actionsList}. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2025. Please check if updated versions of these actions are available that support Node.js 24. For more information see: {Constants.Runner.NodeMigration.Node20DeprecationUrl}"; + var deprecationMessage = $"Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: {actionsList}. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2025. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: {Constants.Runner.NodeMigration.Node20DeprecationUrl}"; context.Warning(deprecationMessage); } } From ad10386d6ac5f18bfb837276086b190238fcf01d Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Wed, 11 Feb 2026 13:46:29 +0000 Subject: [PATCH 5/7] Clarify env vars can be set on runner or in workflow file --- src/Runner.Worker/JobExtension.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Runner.Worker/JobExtension.cs b/src/Runner.Worker/JobExtension.cs index 9168968cf0a..5e23892274b 100644 --- a/src/Runner.Worker/JobExtension.cs +++ b/src/Runner.Worker/JobExtension.cs @@ -740,7 +740,7 @@ public async Task FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRe if (context.Global.DeprecatedNode20Actions?.Count > 0) { var actionsList = string.Join(", ", context.Global.DeprecatedNode20Actions); - var deprecationMessage = $"Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: {actionsList}. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2025. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: {Constants.Runner.NodeMigration.Node20DeprecationUrl}"; + var deprecationMessage = $"Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: {actionsList}. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2025. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: {Constants.Runner.NodeMigration.Node20DeprecationUrl}"; context.Warning(deprecationMessage); } } From 747bd95bf5b08568ef45bf9d690989550d7c8561 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Wed, 11 Feb 2026 13:47:09 +0000 Subject: [PATCH 6/7] Sort deprecated actions list for deterministic annotation output --- src/Runner.Worker/JobExtension.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Runner.Worker/JobExtension.cs b/src/Runner.Worker/JobExtension.cs index 5e23892274b..bd464476629 100644 --- a/src/Runner.Worker/JobExtension.cs +++ b/src/Runner.Worker/JobExtension.cs @@ -739,7 +739,8 @@ public async Task FinalizeJob(IExecutionContext jobContext, Pipelines.AgentJobRe // Add deprecation warning annotation for Node.js 20 actions if (context.Global.DeprecatedNode20Actions?.Count > 0) { - var actionsList = string.Join(", ", context.Global.DeprecatedNode20Actions); + var sortedActions = context.Global.DeprecatedNode20Actions.OrderBy(a => a, StringComparer.OrdinalIgnoreCase); + var actionsList = string.Join(", ", sortedActions); var deprecationMessage = $"Node.js 20 actions are deprecated. The following actions are running on Node.js 20 and may not work as expected: {actionsList}. Actions will be forced to run with Node.js 24 by default starting June 2nd, 2025. Please check if updated versions of these actions are available that support Node.js 24. To opt into Node.js 24 now, set the FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true environment variable on the runner or in your workflow file. Once Node.js 24 becomes the default, you can temporarily opt out by setting ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true. For more information see: {Constants.Runner.NodeMigration.Node20DeprecationUrl}"; context.Warning(deprecationMessage); } From 6f9dda01d7cd6cbf8f475feb54e2b1942f13ba09 Mon Sep 17 00:00:00 2001 From: Salman Muin Kayser Chishti Date: Thu, 19 Feb 2026 17:00:18 +0000 Subject: [PATCH 7/7] Handle local actions in Node 20 deprecation warning --- src/Runner.Worker/Handlers/HandlerFactory.cs | 11 +++- src/Test/L0/Worker/HandlerFactoryL0.cs | 53 ++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/Runner.Worker/Handlers/HandlerFactory.cs b/src/Runner.Worker/Handlers/HandlerFactory.cs index 41580103f7f..e9e2a5a6011 100644 --- a/src/Runner.Worker/Handlers/HandlerFactory.cs +++ b/src/Runner.Worker/Handlers/HandlerFactory.cs @@ -149,10 +149,17 @@ private static string GetActionName(Pipelines.ActionStepDefinitionReference acti { if (action is Pipelines.RepositoryPathReference repoRef) { - var pathString = string.IsNullOrEmpty(repoRef.Path) ? string.Empty : $"/{repoRef.Path}"; - return string.IsNullOrEmpty(repoRef.Ref) + var pathString = string.Empty; + if (!string.IsNullOrEmpty(repoRef.Path)) + { + pathString = string.IsNullOrEmpty(repoRef.Name) + ? repoRef.Path + : $"/{repoRef.Path}"; + } + var repoString = string.IsNullOrEmpty(repoRef.Ref) ? $"{repoRef.Name}{pathString}" : $"{repoRef.Name}{pathString}@{repoRef.Ref}"; + return string.IsNullOrEmpty(repoString) ? null : repoString; } return null; diff --git a/src/Test/L0/Worker/HandlerFactoryL0.cs b/src/Test/L0/Worker/HandlerFactoryL0.cs index 4cfb70bc5dd..85a70ff5284 100644 --- a/src/Test/L0/Worker/HandlerFactoryL0.cs +++ b/src/Test/L0/Worker/HandlerFactoryL0.cs @@ -317,5 +317,58 @@ public void Node12Action_TrackedAsDeprecatedWhenWarnFlagEnabled() Assert.Contains("some-org/old-action@v1", deprecatedActions); } } + + [Fact] + [Trait("Level", "L0")] + [Trait("Category", "Worker")] + public void LocalNode20Action_TrackedWhenWarnFlagEnabled() + { + using (TestHostContext hc = CreateTestContext()) + { + // Arrange. + var hf = new HandlerFactory(); + hf.Initialize(hc); + + var variables = new Dictionary + { + { Constants.Runner.NodeMigration.WarnOnNode20Flag, new VariableValue("true") } + }; + Variables serverVariables = new(hc, variables); + var deprecatedActions = new HashSet(StringComparer.OrdinalIgnoreCase); + + _ec.Setup(x => x.Global).Returns(new GlobalContext() + { + Variables = serverVariables, + EnvironmentVariables = new Dictionary(), + DeprecatedNode20Actions = deprecatedActions + }); + + // Local action: Name is empty, Path is the local path + var actionRef = new RepositoryPathReference + { + Name = "", + Path = "./.github/actions/my-action", + RepositoryType = "self" + }; + + // Act. + var data = new NodeJSActionExecutionData(); + data.NodeVersion = "node20"; + hf.Create( + _ec.Object, + actionRef, + new Mock().Object, + data, + new Dictionary(), + new Dictionary(), + new Variables(hc, new Dictionary()), + "", + new List() + ); + + // Assert - local action should be tracked with its path + Assert.Contains("./.github/actions/my-action", deprecatedActions); + } + } } }