diff --git a/.github/workflows/ci-coach.lock.yml b/.github/workflows/ci-coach.lock.yml index 5938abfb7d1..b51950a0e3f 100644 --- a/.github/workflows/ci-coach.lock.yml +++ b/.github/workflows/ci-coach.lock.yml @@ -359,6 +359,16 @@ jobs: cache: 'npm' cache-dependency-path: 'actions/setup/js/package-lock.json' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise diff --git a/.github/workflows/copilot-token-audit.lock.yml b/.github/workflows/copilot-token-audit.lock.yml index f602b947022..baa5bb9247d 100644 --- a/.github/workflows/copilot-token-audit.lock.yml +++ b/.github/workflows/copilot-token-audit.lock.yml @@ -408,6 +408,16 @@ jobs: cache: 'npm' cache-dependency-path: 'actions/setup/js/package-lock.json' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: diff --git a/.github/workflows/copilot-token-optimizer.lock.yml b/.github/workflows/copilot-token-optimizer.lock.yml index c89a101b30d..b72c3989c34 100644 --- a/.github/workflows/copilot-token-optimizer.lock.yml +++ b/.github/workflows/copilot-token-optimizer.lock.yml @@ -361,6 +361,16 @@ jobs: cache: 'npm' cache-dependency-path: 'actions/setup/js/package-lock.json' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise diff --git a/.github/workflows/daily-fact.lock.yml b/.github/workflows/daily-fact.lock.yml index fc76a76d58d..4f15965e89d 100644 --- a/.github/workflows/daily-fact.lock.yml +++ b/.github/workflows/daily-fact.lock.yml @@ -455,6 +455,16 @@ jobs: with: node-version: '24' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: diff --git a/.github/workflows/daily-hippo-learn.lock.yml b/.github/workflows/daily-hippo-learn.lock.yml index 0f818149e45..be156cb1bf8 100644 --- a/.github/workflows/daily-hippo-learn.lock.yml +++ b/.github/workflows/daily-hippo-learn.lock.yml @@ -345,6 +345,16 @@ jobs: with: node-version: '22' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise diff --git a/.github/workflows/daily-issues-report.lock.yml b/.github/workflows/daily-issues-report.lock.yml index 0da4862d2a2..7980e7d1dee 100644 --- a/.github/workflows/daily-issues-report.lock.yml +++ b/.github/workflows/daily-issues-report.lock.yml @@ -383,6 +383,16 @@ jobs: with: node-version: '24' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: diff --git a/.github/workflows/daily-multi-device-docs-tester.lock.yml b/.github/workflows/daily-multi-device-docs-tester.lock.yml index 17eb321e8b4..d987f2d5fd5 100644 --- a/.github/workflows/daily-multi-device-docs-tester.lock.yml +++ b/.github/workflows/daily-multi-device-docs-tester.lock.yml @@ -362,6 +362,16 @@ jobs: with: node-version: '24' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise diff --git a/.github/workflows/daily-news.lock.yml b/.github/workflows/daily-news.lock.yml index 114edd30501..c745449c7b5 100644 --- a/.github/workflows/daily-news.lock.yml +++ b/.github/workflows/daily-news.lock.yml @@ -381,6 +381,16 @@ jobs: with: node-version: '24' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: diff --git a/.github/workflows/docs-noob-tester.lock.yml b/.github/workflows/docs-noob-tester.lock.yml index f728d8db99a..fda2be09702 100644 --- a/.github/workflows/docs-noob-tester.lock.yml +++ b/.github/workflows/docs-noob-tester.lock.yml @@ -341,6 +341,16 @@ jobs: with: node-version: '22' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise diff --git a/.github/workflows/go-logger.lock.yml b/.github/workflows/go-logger.lock.yml index 470080bb1e6..672aadf68a5 100644 --- a/.github/workflows/go-logger.lock.yml +++ b/.github/workflows/go-logger.lock.yml @@ -349,6 +349,16 @@ jobs: cache: 'npm' cache-dependency-path: 'actions/setup/js/package-lock.json' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise diff --git a/.github/workflows/hourly-ci-cleaner.lock.yml b/.github/workflows/hourly-ci-cleaner.lock.yml index 2c237c6e164..6db7c36557e 100644 --- a/.github/workflows/hourly-ci-cleaner.lock.yml +++ b/.github/workflows/hourly-ci-cleaner.lock.yml @@ -365,6 +365,16 @@ jobs: cache: 'npm' cache-dependency-path: 'actions/setup/js/package-lock.json' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise diff --git a/.github/workflows/jsweep.lock.yml b/.github/workflows/jsweep.lock.yml index e9e4c1f7710..2b8eb618357 100644 --- a/.github/workflows/jsweep.lock.yml +++ b/.github/workflows/jsweep.lock.yml @@ -385,6 +385,16 @@ jobs: with: node-version: '20' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise diff --git a/.github/workflows/mcp-inspector.lock.yml b/.github/workflows/mcp-inspector.lock.yml index 64b347db35f..4b20f3b06e6 100644 --- a/.github/workflows/mcp-inspector.lock.yml +++ b/.github/workflows/mcp-inspector.lock.yml @@ -469,6 +469,16 @@ jobs: with: node-version: '24' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: diff --git a/.github/workflows/smoke-test-tools.lock.yml b/.github/workflows/smoke-test-tools.lock.yml index 9dd9745a6c5..eae4c66b220 100644 --- a/.github/workflows/smoke-test-tools.lock.yml +++ b/.github/workflows/smoke-test-tools.lock.yml @@ -405,6 +405,16 @@ jobs: with: node-version: '20' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Setup Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: diff --git a/.github/workflows/technical-doc-writer.lock.yml b/.github/workflows/technical-doc-writer.lock.yml index 11af5ec7571..cdbec36bc9c 100644 --- a/.github/workflows/technical-doc-writer.lock.yml +++ b/.github/workflows/technical-doc-writer.lock.yml @@ -382,6 +382,16 @@ jobs: cache: 'npm' cache-dependency-path: 'docs/package-lock.json' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Create gh-aw temp directory run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" - name: Configure gh CLI for GitHub Enterprise diff --git a/.github/workflows/unbloat-docs.lock.yml b/.github/workflows/unbloat-docs.lock.yml index 13e4b99888f..b3ae5635ea3 100644 --- a/.github/workflows/unbloat-docs.lock.yml +++ b/.github/workflows/unbloat-docs.lock.yml @@ -414,6 +414,16 @@ jobs: cache: 'npm' cache-dependency-path: 'docs/package-lock.json' package-manager-cache: false + - name: Copy node to standard system path for AWF sandbox + run: | + # AWF chroot container may not include the toolcache path where + # actions/setup-node installs Node.js on custom image runners. + # Copy to /usr/local/bin/node to ensure it's accessible inside AWF. + NODE_BIN="$(command -v node 2>/dev/null || true)" + if [ -n "$NODE_BIN" ] && [ "$NODE_BIN" != "/usr/local/bin/node" ]; then + sudo cp -f "$NODE_BIN" /usr/local/bin/node + sudo chmod +x /usr/local/bin/node + fi - name: Install dependencies run: npm ci working-directory: ./docs diff --git a/pkg/workflow/runtime_setup_test.go b/pkg/workflow/runtime_setup_test.go index f77faf12088..b40fdf31317 100644 --- a/pkg/workflow/runtime_setup_test.go +++ b/pkg/workflow/runtime_setup_test.go @@ -296,11 +296,12 @@ func TestGenerateRuntimeSetupSteps(t *testing.T) { requirements: []RuntimeRequirement{ {Runtime: findRuntimeByID("node"), Version: "20"}, }, - expectSteps: 1, + expectSteps: 2, // setup + copy to standard system path for AWF chroot mode checkContent: []string{ "Setup Node.js", "actions/setup-node@", "node-version: '20'", + "Copy node to standard system path for AWF sandbox", }, }, { @@ -381,10 +382,11 @@ func TestGenerateRuntimeSetupSteps(t *testing.T) { {Runtime: findRuntimeByID("node"), Version: "24"}, {Runtime: findRuntimeByID("python"), Version: "3.12"}, }, - expectSteps: 2, + expectSteps: 3, // node setup + node system path + python setup checkContent: []string{ "Setup Node.js", "Setup Python", + "Copy node to standard system path for AWF sandbox", }, }, { @@ -392,9 +394,10 @@ func TestGenerateRuntimeSetupSteps(t *testing.T) { requirements: []RuntimeRequirement{ {Runtime: findRuntimeByID("node"), Version: ""}, }, - expectSteps: 1, + expectSteps: 2, // setup + copy to standard system path for AWF chroot mode checkContent: []string{ "node-version: '24'", + "Copy node to standard system path for AWF sandbox", }, }, { @@ -818,12 +821,13 @@ func TestGenerateRuntimeSetupStepsWithIfCondition(t *testing.T) { IfCondition: "hashFiles('package.json') != ''", }, }, - expectSteps: 1, + expectSteps: 2, // setup + copy to standard system path for AWF chroot mode checkContent: []string{ "Setup Node.js", "actions/setup-node@", "node-version: '20'", "if: hashFiles('package.json') != ''", + "Copy node to standard system path for AWF sandbox", }, }, { @@ -845,7 +849,7 @@ func TestGenerateRuntimeSetupStepsWithIfCondition(t *testing.T) { IfCondition: "hashFiles('package.json') != ''", }, }, - expectSteps: 4, // go setup + GOROOT capture + python setup + node setup + expectSteps: 5, // go setup + GOROOT capture + python setup + node setup + node copy checkContent: []string{ "Setup Go", "if: hashFiles('go.mod') != ''", @@ -853,6 +857,7 @@ func TestGenerateRuntimeSetupStepsWithIfCondition(t *testing.T) { "if: hashFiles('requirements.txt') != ''", "Setup Node.js", "if: hashFiles('package.json') != ''", + "Copy node to standard system path for AWF sandbox", }, }, } diff --git a/pkg/workflow/runtime_step_generator.go b/pkg/workflow/runtime_step_generator.go index eace9904787..52b98884bba 100644 --- a/pkg/workflow/runtime_step_generator.go +++ b/pkg/workflow/runtime_step_generator.go @@ -20,7 +20,7 @@ func GenerateRuntimeSetupSteps(requirements []RuntimeRequirement) []GitHubAction steps = append(steps, generateSetupStep(&req)) // Add environment variable capture steps after setup actions for AWF chroot mode. - // Most env vars are inherited via AWF_HOST_PATH, but Go is special. + // Most env vars are inherited via AWF_HOST_PATH, but some runtimes need extra work. switch req.Runtime.ID { case "go": // GitHub Actions uses "trimmed" Go binaries that require GOROOT to be explicitly set. @@ -29,8 +29,19 @@ func GenerateRuntimeSetupSteps(requirements []RuntimeRequirement) []GitHubAction // environment, so we must capture it explicitly. runtimeStepGeneratorLog.Print("Adding GOROOT capture step for chroot mode compatibility") steps = append(steps, generateEnvCaptureStep("GOROOT", "go env GOROOT")) + case "node": + // actions/setup-node installs Node.js into the toolcache directory + // (e.g., /home/runner/work/_tool/node/24.x.x/x64/bin/node) on custom image + // runners. AWF's chroot container may not have access to these toolcache paths + // on certain runner types (e.g., aw-gpu-runner-T4 GPU runners), causing + // `node: command not found` errors inside the AWF sandbox even though PATH is + // set correctly. Copy the node binary to /usr/local/bin/node — a standard + // system path that is always accessible inside AWF's chroot — to ensure that + // the Copilot driver script and other node-dependent scripts can execute. + runtimeStepGeneratorLog.Print("Adding node system path step for AWF chroot mode compatibility") + steps = append(steps, generateNodeSystemPathStep()) } - // Note: Java and .NET don't need capture steps anymore because: + // Note: Java and .NET don't need capture steps because: // - AWF_HOST_PATH captures the complete host PATH including $JAVA_HOME/bin and $DOTNET_ROOT // - AWF's entrypoint.sh exports PATH="${AWF_HOST_PATH}" which preserves all setup-* additions } @@ -49,6 +60,30 @@ func generateEnvCaptureStep(envVar string, captureCmd string) GitHubActionStep { } } +// generateNodeSystemPathStep creates a step that copies the node binary to a standard +// system path (/usr/local/bin/node) after actions/setup-node installs it. +// +// On custom image runners (e.g., aw-gpu-runner-T4 GPU runners), actions/setup-node +// installs Node.js in the toolcache (e.g., /home/runner/work/_tool/node/24.x.x/x64/bin/). +// AWF's chroot container may not have access to these toolcache paths, causing +// `node: command not found` when the Copilot driver script runs inside the sandbox. +// Copying to /usr/local/bin/node ensures the binary is accessible at a well-known +// system path that is always available inside AWF's chroot container. +func generateNodeSystemPathStep() GitHubActionStep { + return GitHubActionStep{ + " - name: Copy node to standard system path for AWF sandbox", + " run: |", + " # AWF chroot container may not include the toolcache path where", + " # actions/setup-node installs Node.js on custom image runners.", + " # Copy to /usr/local/bin/node to ensure it's accessible inside AWF.", + " NODE_BIN=\"$(command -v node 2>/dev/null || true)\"", + " if [ -n \"$NODE_BIN\" ] && [ \"$NODE_BIN\" != \"/usr/local/bin/node\" ]; then", + " sudo cp -f \"$NODE_BIN\" /usr/local/bin/node", + " sudo chmod +x /usr/local/bin/node", + " fi", + } +} + // generateSetupStep creates a setup step for a given runtime requirement func generateSetupStep(req *RuntimeRequirement) GitHubActionStep { runtime := req.Runtime