Skip to content

Conversation

salmanmkc
Copy link
Contributor

@salmanmkc salmanmkc commented Jul 21, 2025

Node 20 to Node 24 Migration Strategy

This is a proposal for the migration strategy, nothing here is 100% confirmed yet.

Overview

This PR implements the migration strategy for GitHub Actions JavaScript/TypeScript actions from Node 20 to Node 24, preparing for Node 20's end of life. The approach uses feature flags for a phased rollout, similar to the previous Node 16 to Node 20 migration.

Key Features

  • Phased migration with clear transition points
  • User controls via environment variables
  • Feature flags for controlled rollout
  • Compatibility handling for Linux ARM32
  • Informative messaging to guide users through the transition

Migration Phases

Phase 1: Node 20 Default

Initially Node 20 remains the default:

  • Default: Node 20
  • Opt-in to Node 24: Set FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true

Phase 2: Node 24 Default

When the actions.runner.usenode24bydefault flag is enabled:

  • Default: Node 24
  • Temporary fallback to Node 20: Set ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true
  • Informational message appears explaining Node 20 deprecation and opt-out options

Phase 3: Node 24 Required

When the actions.runner.requirenode24 flag is enabled:

  • All actions use Node 24
  • No opt-out options available

Environment Variables

The migration uses two environment variables:

  • FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true: Opt into Node 24 during Phase 1
  • ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true: Fall back to Node 20 during Phase 2

These can be set at either the workflow level (in YAML) or runner level (system environment). Workflow settings override runner settings to ensure job-specific control.

Implementation Details

Core Components

The migration strategy includes:

  1. Centralized Constants

    • Version identifiers: Node20 and Node24
    • Environment variable definitions
    • Feature flag definitions
  2. Version Selection Logic

    • Logic that factors in:
      • Current migration phase
      • Environment variables (workflow and runner)
      • Action metadata
      • Platform compatibility
  3. User Feedback

    • Warning when conflicting settings are detected
    • Helpful messages during Phase 2 explaining the Node version change
    • Clear instructions for temporarily using Node 20 when needed
  4. Platform Compatibility

    • Special handling for Linux ARM32 where Node 24 isn't supported
    • Automatic fallback to Node 20 with explanatory message
    • Multiple safeguards to ensure consistent behavior

Testing & Compatibility

Tests cover:

  • Various feature flag and environment variable combinations
  • All migration phases
  • Environment variable precedence scenarios
  • Platform-specific behavior (including ARM32)
  • Conflict detection

Backward compatibility is maintained through:

  • Zero-change workflow execution during migration
  • Clear warning messages for configuration issues
  • Automatic fallbacks on unsupported platforms
  • Feature-flagged rollout for controlled deployment

Node Version Selection Logic - Truth Table

Variable Precedence

  • Workflow-level variables always take precedence over System-level variables for the same variable
  • Each variable (FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 and ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION) is evaluated independently
  • Variables from different sources can be combined in the final decision

Phase 1: Node 20 Default (No Feature Flags)

ForceNode24 (Workflow) AllowUnsecure (Workflow) ForceNode24 (System) AllowUnsecure (System) Resulting Node Version Notes
true true - - Node 20 Warning: conflicting settings
true false - - Node 24
true - - - Node 24
false true - - Node 20
false false - - Node 20
false - - - Node 20
- true - - Node 20
- false - - Node 20
- - true true Node 20 Warning: conflicting settings
- - true false Node 24
- - true - Node 24
- - false true Node 20
- - false false Node 20
- - false - Node 20
- - - true Node 20
- - - false Node 20
- - - - Node 20 Default

Phase 2: Node 24 Default (useNode24ByDefault=true)

ForceNode24 (Workflow) AllowUnsecure (Workflow) ForceNode24 (System) AllowUnsecure (System) Resulting Node Version Notes
true true - - Node 20 Warning: conflicting settings
true false - - Node 24
true - - - Node 24
false true - - Node 20
false false - - Node 24
false - - - Node 24
- true - - Node 20
- false - - Node 24
- - true true Node 20 Warning: conflicting settings
- - true false Node 24
- - true - Node 24
- - false true Node 20
- - false false Node 24
- - false - Node 24
- - - true Node 20
- - - false Node 24
- - - - Node 24 Default
true - - true Node 20 Important edge case: AllowUnsecure=true always takes precedence in Phase 2
- true true - Node 20 Important edge case: AllowUnsecure=true always takes precedence in Phase 2

Phase 3: Node 24 Required (requireNode24=true)

In Phase 3, the environment variables are ignored and Node 24 is always used.

Notable Edge Cases

1. Mixed-Source Variables (Workflow vs System)

When variables come from different sources, each is evaluated independently:

Example: ForceNode24=false (Workflow), AllowUnsecure=true (System)

  • The workflow explicitly opts out of Node 24 by setting ForceNode24=false
  • The system has AllowUnsecure=true
  • Result: Node 20 is used (workflow's opt-out from Node 24 is respected)

Example: AllowUnsecure=false (Workflow), ForceNode24=true (System)

  • The workflow explicitly disallows Node 20 by setting AllowUnsecure=false
  • The system has ForceNode24=true
  • Result: Node 24 is used (system's ForceNode24 is respected since workflow doesn't override it)

Key Edge Case in Phase 2: When both variables are true (from any sources)

  • If ForceNode24=true (Workflow) and AllowUnsecure=true (System)
  • Or ForceNode24=true (System) and AllowUnsecure=true (Workflow)
  • Result: Node 20 is used because AllowUnsecure=true takes precedence in Phase 2
  • This is logical since in Phase 2, Node 24 is already the default, so the request to use Node 20 is more important

2. Conflicting Settings

When both variables are set to true from the same source (both from workflow or both from system):

  • Warning message is displayed
  • Default version for the current phase is used
  • Workflow level conflicts trigger the same behavior as system level conflicts

3. ARM32 Linux Platform Compatibility

For Linux ARM32 platforms, Node 24 is not supported:

  • If Node 24 would normally be selected, it automatically falls back to Node 20
  • Warning message is displayed explaining the compatibility issue
  • This occurs regardless of environment variables or feature flags

4. Phase 2 User Notification

In Phase 2, when Node 24 is selected:

  • Users see an informational message explaining that Node 20 is deprecated
  • Instructions are provided for temporarily using Node 20 if needed

Environment Variable Examples

Opt-in to Node 24 using workflow environment variable

You can opt in to Node 24 during Phase 1 by adding the environment variable to your workflow:

env:
  FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

jobs:
  build:
    runs-on: node24
    
    steps:
      - uses: actions/checkout@v4

This forces actions like actions/checkout@v4 (which normally uses Node 20) to run with Node 24 instead:

Opt in using FORCE_JAVASCRIPT_ACTIONS_TO_NODE24 workflow env variable

Opt-in to Node 24 using system environment variable

You can also opt in at the runner level by setting the system environment variable:

Using system variable for opt in

This is how it behaves when node24 is default

Node 24 default on non-node 20 actions

When node24 is default, actions can be opted out using the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION environment variable

ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION

For example:


on:
  workflow_dispatch:

env:
  ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION: true

jobs:
  build:
    runs-on: node24
    
    steps:
      - uses: actions/checkout@v4

Opt out of node 24 default

Environment Variable Precedence

When both system-level and workflow-level environment variables are set:

  1. Workflow-level variables always take precedence over system-level variables for the same variable
  2. Variables are evaluated independently based on their source
  3. In Phase 1 (Node 20 default):
    • If FORCE_JAVASCRIPT_ACTIONS_TO_NODE24=true, Node 24 is used
    • Otherwise, Node 20 is used
  4. In Phase 2 (Node 24 default):
    • If ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true, Node 20 is used
    • Otherwise, Node 24 is used

@salilsub
Copy link

I think that if someone sets both of the variables to true, we should default to the same logic that is used when neither is set. Setting both should only happen with user error, I think it's more intuitive that we pick the default in that case.
Looking at it again, I also think that if someone sets both, we probably should add an annotation to say both variables are being set and what version is being defaulted to.

…g if both env variables are enabled by the user
@salmanmkc
Copy link
Contributor Author

I think that if someone sets both of the variables to true, we should default to the same logic that is used when neither is set. Setting both should only happen with user error, I think it's more intuitive that we pick the default in that case. Looking at it again, I also think that if someone sets both, we probably should add an annotation to say both variables are being set and what version is being defaulted to.

That makes sense, modified it

@salmanmkc salmanmkc changed the title Env variables for node 24 and node 20 migrations Node 20 -> Node 24 migration feature flagging, opt-in and opt-out environment variables Jul 22, 2025
@salmanmkc salmanmkc marked this pull request as ready for review August 6, 2025 16:30
@Copilot Copilot AI review requested due to automatic review settings August 6, 2025 16:30
@salmanmkc salmanmkc requested a review from a team as a code owner August 6, 2025 16:31
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR implements a phased migration strategy for JavaScript/TypeScript actions from Node 20 to Node 24 in GitHub Actions runners. The implementation introduces feature flags for controlled rollout with user controls via environment variables.

Key changes:

  • Centralized constants for Node versions, environment variables, and feature flags
  • Node version selection logic with three distinct migration phases
  • Environment variable precedence handling (workflow over system)

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/Runner.Common/Constants.cs Adds NodeMigration constants for versions, environment variables, and feature flags
src/Runner.Common/Util/NodeUtil.cs Implements core node version determination logic with ARM32 compatibility checks
src/Runner.Worker/FeatureManager.cs Adds generic feature flag checking methods for migration control
src/Runner.Worker/Handlers/HandlerFactory.cs Integrates node version selection logic into action handler creation
src/Test/L0/Util/NodeUtilL0.cs Comprehensive unit tests covering all migration phases and edge cases
Comments suppressed due to low confidence (4)

src/Test/L0/Util/NodeUtilL0.cs:24

  • This test case doesn't verify that environment variables are actually ignored in Phase 3. Consider adding an assertion to confirm that the environment variables don't affect the outcome when requireNode24=true.
        [InlineData(true, false, true, true, "node24", false)]     // Phase 3: Always Node 24 regardless of env vars, no warnings in Phase 3

src/Runner.Worker/Handlers/HandlerFactory.cs:93

  • The word 'UNSECURE' in the environment variable name should be 'INSECURE' for correct spelling.
                        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.";

src/Runner.Common/Constants.cs:183

  • The word 'UNSECURE' in the environment variable name should be 'INSECURE' for correct spelling.
                public static readonly string AllowUnsecureNodeVersionVariable = "ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION";

src/Runner.Common/Util/NodeUtil.cs:79

  • The word 'UNSECURE' in the environment variable name should be 'INSECURE' for correct spelling.
                warningMessage = $"Both {Constants.Runner.NodeMigration.ForceNode24Variable} and {Constants.Runner.NodeMigration.AllowUnsecureNodeVersionVariable} environment variables are set to true. This is likely a configuration error. Using the default Node version: {defaultVersion}.";

@salmanmkc salmanmkc enabled auto-merge (squash) August 7, 2025 16:26
@salmanmkc salmanmkc merged commit 0203cf2 into main Aug 7, 2025
9 checks passed
@salmanmkc salmanmkc deleted the salmanmkc/3-env-variables-node-20-node-24 branch August 7, 2025 16:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants