Skip to content

Fix codex engine on self-hosted runners: prefer npm-installed binary over vendored system binary#20161

Closed
Copilot wants to merge 4 commits intomainfrom
copilot/fix-codex-engine-arg-error
Closed

Fix codex engine on self-hosted runners: prefer npm-installed binary over vendored system binary#20161
Copilot wants to merge 4 commits intomainfrom
copilot/fix-codex-engine-arg-error

Conversation

Copy link
Contributor

Copilot AI commented Mar 9, 2026

On self-hosted runners with a pre-installed (vendored) codex binary, the older system binary shadows the workflow-installed @openai/codex@latest in PATH, causing failures when the old binary doesn't support --dangerously-bypass-approvals-and-sandbox.

Changes

  • GetNpmBinPathSetup() (AWF mode): Prepend the npm global prefix bin directory before hostedtoolcache entries so the npm-installed binary wins over any system-installed one. Previously only /opt/hostedtoolcache/*/bin was added, which doesn't exist on most self-hosted runners. Uses a temp variable and ${_npm_prefix:+${_npm_prefix}/bin:} parameter expansion to safely guard against an empty prefix (avoids injecting a bare /bin into PATH when npm is unavailable).

  • Non-AWF execution commands in codex_engine.go: Replaced the separate inline PATH export with GetNpmBinPathSetup(), making it the single source of truth for PATH setup in both AWF and non-AWF modes. Both agent-file and standard execution variants use the same function.

# Both non-AWF command variants now start with:
set -o pipefail
_npm_prefix="$(npm config get prefix 2>/dev/null || true)"
export PATH="${_npm_prefix:+${_npm_prefix}/bin:}$(find /opt/hostedtoolcache ...)$PATH"
# ...then run codex exec --dangerously-bypass-approvals-and-sandbox ...

On GitHub-hosted runners the npm prefix (/usr/local/bin) is already in PATH, making this a no-op. On self-hosted runners it guarantees the freshly-installed latest binary is resolved first.

  • Tests: Updated TestGetNpmBinPathSetup to assert the _npm_prefix temp var and ${_npm_prefix:+...} expansion are present. Refreshed TestGetNpmBinPathSetup_GorootOrdering to derive from the actual function output. Added TestGetNpmBinPathSetup_EmptyNpmPrefixDoesNotInjectBin verifying the empty-prefix guard. Added TestCodexEngineNonAWFNpmPathSetup covering both standard and agent-file non-AWF execution paths.

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.


Changeset

  • Type: patch
  • Description: Prefer the npm-installed Codex CLI on self-hosted runners by ensuring both AWF and non-AWF commands prepend the npm prefix so the workflow-installed binary (with the required sandbox flag) runs before any vendored system binary.

Generated by Changeset Generator for issue #20161 ·

Warning

⚠️ Firewall blocked 2 domains

The following domains were blocked by the firewall during workflow execution:

  • ab.chatgpt.com
  • github.com

To allow these domains, add them to the network.allowed list in your workflow frontmatter:

network:
  allowed:
    - defaults
    - "ab.chatgpt.com"
    - "github.com"

See Network Configuration for more information.



✨ PR Review Safe Output Test - Run 22849956573

💥 [THE END] — Illustrated by Smoke Claude ·



✨ PR Review Safe Output Test - Run 22853945663

💥 [THE END] — Illustrated by Smoke Claude ·

…up to prefer workflow-installed binary over vendored system binary

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix Codex engine argument error on self-hosted runners Fix codex engine on self-hosted runners: prefer npm-installed binary over vendored system binary Mar 9, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

Hey @Copilot 👋 — great work on this fix! Addressing the PATH precedence issue for self-hosted runners with vendored codex binaries is exactly the kind of targeted, high-impact bug fix this codebase needs. The root cause diagnosis is solid and the solution is clean.

This PR is well-aligned with the contribution guidelines. Here's a quick summary of the checklist:

Check Result
On-topic ✅ Core engine bug fix
Follows process ✅ Agent-created PR under core team member supervision
Focused ✅ Single, cohesive concern
New dependencies ✅ None added
Tests TestCodexEngineNonAWFNpmPathSetup + updated TestGetNpmBinPathSetup
Description ✅ Detailed with context, code snippets, and scope
Diff size 105 lines (97 additions, 8 deletions)

One minor note: the PR is currently in draft state — remember to mark it ready for review once any final validation passes.

Everything looks good — this PR is ready for maintainer review once it's promoted out of draft! 🚀

Generated by Contribution Check ·

@github-actions github-actions bot added the lgtm label Mar 9, 2026
@pelikhan pelikhan marked this pull request as ready for review March 9, 2026 10:50
Copilot AI review requested due to automatic review settings March 9, 2026 10:50
@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

Smoke Project encountered failures. Check the logs for details.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

📰 DEVELOPING STORY: Smoke Copilot ARM64 reports was cancelled. Our correspondents are investigating the incident...

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

Smoke Temporary ID encountered failures. Check the logs for details.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

Smoke Multi PR failed to create multiple PRs. Check the logs.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

📰 DEVELOPING STORY: Smoke Copilot reports was cancelled. Our correspondents are investigating the incident...

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

🌑 The shadows whisper... Smoke Codex failed. The oracle requires further meditation...

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

📰 BREAKING: Smoke Copilot is now investigating this pull request. Sources say the story is developing...

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

✅ All tools validated successfully! Agent Container Smoke Test confirms agent container is ready.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

🎬 THE ENDSmoke Claude MISSION: ACCOMPLISHED! The hero saves the day! ✨

Copy link
Contributor

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 fixes Codex CLI resolution on self-hosted runners by ensuring the workflow-installed npm global binary takes precedence over any preinstalled/vendored codex already present on the runner’s PATH.

Changes:

  • Updated GetNpmBinPathSetup() to prepend the npm global prefix bin directory ahead of hostedtoolcache paths.
  • Injected the same npm-prefix PATH setup into Codex non-AWF execution commands (both standard and agent-file variants).
  • Updated and added tests to assert PATH setup ordering and non-AWF command inclusion.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
pkg/workflow/engine_helpers.go Prepends npm global prefix bin to PATH in GetNpmBinPathSetup() before hostedtoolcache bins.
pkg/workflow/codex_engine.go Adds npm-prefix PATH export to non-AWF Codex execution command templates.
pkg/workflow/engine_helpers_test.go Extends PATH-setup assertions and refactors the “no GOROOT” chain test to use the function output.
pkg/workflow/codex_engine_test.go Adds coverage ensuring non-AWF Codex execution includes npm prefix PATH setup.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

// ensures the Go version set by actions/setup-go takes precedence.
// AWF's entrypoint.sh exports GOROOT before the user command runs.
return `export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\n' ':')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true`
return `export PATH="$(npm config get prefix 2>/dev/null)/bin:$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\n' ':')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true`
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

$(npm config get prefix)/bin will expand to /bin when npm config get prefix returns an empty string (or npm is unavailable), which unintentionally prepends /bin and can change command resolution. Consider using npm bin -g (returns the bin dir directly) or guarding with a non-empty prefix (e.g., only prepend when the computed prefix/bin path is non-empty) to avoid injecting /bin: into PATH.

Suggested change
return `export PATH="$(npm config get prefix 2>/dev/null)/bin:$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\n' ':')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true`
return `NPM_PREFIX="$(npm config get prefix 2>/dev/null || true)"; HOSTED_BINS="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\n' ':')"; if [ -n "$NPM_PREFIX" ]; then export PATH="$NPM_PREFIX/bin:${HOSTED_BINS}$PATH"; else export PATH="${HOSTED_BINS}$PATH"; fi; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true`

Copilot uses AI. Check for mistakes.
Comment on lines +253 to +256
// Prepend the npm global prefix bin to PATH so the workflow-installed (latest) codex
// binary takes precedence over any vendored/system-installed binary on self-hosted runners.
// This mirrors the PATH setup used in AWF mode via GetNpmBinPathSetup().
npmPrefixSetup := `export PATH="$(npm config get prefix 2>/dev/null)/bin:$PATH"`
Copy link

Copilot AI Mar 9, 2026

Choose a reason for hiding this comment

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

The PATH setup here has the same /bin injection edge case as GetNpmBinPathSetup: if npm config get prefix is empty (or npm isn't found), the export becomes PATH="/bin:$PATH", which can unintentionally change command resolution. Prefer npm bin -g or conditionally prepend only when the computed directory is non-empty.

Suggested change
// Prepend the npm global prefix bin to PATH so the workflow-installed (latest) codex
// binary takes precedence over any vendored/system-installed binary on self-hosted runners.
// This mirrors the PATH setup used in AWF mode via GetNpmBinPathSetup().
npmPrefixSetup := `export PATH="$(npm config get prefix 2>/dev/null)/bin:$PATH"`
// Prepend the npm global bin directory to PATH so the workflow-installed (latest) codex
// binary takes precedence over any vendored/system-installed binary on self-hosted runners.
// This mirrors the PATH setup used in AWF mode via GetNpmBinPathSetup().
npmPrefixSetup := `npm_bin_dir="$(npm bin -g 2>/dev/null || true)"
if [ -n "$npm_bin_dir" ]; then
export PATH="$npm_bin_dir:$PATH"
fi`

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

Commit pushed: 3ac1a81

Generated by Changeset Generator

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

Agent Container Tool Check

Tool Status Version
bash 5.2.21
sh available
git 2.53.0
jq 1.7
yq v4.52.4
curl 8.5.0
gh 2.87.3
node v20.20.0
python3 3.12.3
go 1.24.13
java 21.0.10
dotnet 10.0.102

Result: 12/12 tools available ✅

Overall Status: PASS

🔧 Tool validation by Agent Container Smoke Test ·

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

Smoke test 22849956541 by @pelikhan:

  • ✅ GitHub MCP | ✅ GH CLI | ❌ Serena MCP | ✅ Playwright | ✅ Web Fetch
  • ✅ File Write | ✅ Bash | ✅ Discussion | ✅ Build | ✅ PR Review

Overall: ⚠️ PARTIAL PASS (Serena MCP tool not available)

📰 BREAKING: Report filed by Smoke Copilot ·

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

This PR correctly fixes the codex path resolution issue on self-hosted runners by prepending the npm global prefix bin directory to PATH in both AWF and non-AWF execution paths. The fix is clean, well-documented, and includes comprehensive tests. No concerns.

📰 BREAKING: Report filed by Smoke Copilot

// ensures the Go version set by actions/setup-go takes precedence.
// AWF's entrypoint.sh exports GOROOT before the user command runs.
return `export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\n' ':')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true`
return `export PATH="$(npm config get prefix 2>/dev/null)/bin:$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\n' ':')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true`
Copy link
Contributor

Choose a reason for hiding this comment

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

Prepending the npm global prefix bin first is the correct fix for the self-hosted runner issue. This ensures the workflow-installed codex binary (with --dangerously-bypass-approvals-and-sandbox) takes precedence over any vendored binary in the system PATH.

} else {
// Build the command without AWF wrapping
// Reuse commandName already determined above
//
Copy link
Contributor

Choose a reason for hiding this comment

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

Good defensive approach - using 2>/dev/null with the npm command prevents errors from causing issues when npm isn't available in the PATH. The fallback gracefully results in an empty prefix which keeps the PATH setup functional.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 9, 2026

📰 VERDICT: Smoke Copilot has concluded. All systems operational. This is a developing story. 🎤

@pelikhan
Copy link
Contributor

pelikhan commented Mar 9, 2026

@copilot there are multiple patching of PATH in a single command, can it be made cleaner?

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

💥 Automated smoke test review - all systems nominal!

💥 [THE END] — Illustrated by Smoke Claude

// Prepend the npm global prefix bin to PATH so the workflow-installed (latest) codex
// binary takes precedence over any vendored/system-installed binary on self-hosted runners.
// This mirrors the PATH setup used in AWF mode via GetNpmBinPathSetup().
npmPrefixSetup := `export PATH="$(npm config get prefix 2>/dev/null)/bin:$PATH"`
Copy link
Contributor

Choose a reason for hiding this comment

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

Good fix! Prepending the npm global bin ensures the workflow-installed Codex takes precedence. This is consistent with the AWF path setup in GetNpmBinPathSetup().

// ensures the Go version set by actions/setup-go takes precedence.
// AWF's entrypoint.sh exports GOROOT before the user command runs.
return `export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\n' ':')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true`
return `export PATH="$(npm config get prefix 2>/dev/null)/bin:$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\n' ':')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true`
Copy link
Contributor

Choose a reason for hiding this comment

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

The npm prefix is now correctly prepended before hostedtoolcache entries. This ensures the latest npm-installed binary wins over any vendored system binary on self-hosted runners.

…ween AWF and non-AWF codex modes

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 9, 2026

@copilot there are multiple patching of PATH in a single command, can it be made cleaner?

Done in 33e43ac. The non-AWF execution now reuses GetNpmBinPathSetup() directly instead of maintaining a separate npmPrefixSetup variable, making it the single source of truth for PATH setup in both AWF and non-AWF modes. Additionally, the previous $(npm config get prefix 2>/dev/null)/bin pattern (which could inject a bare /bin when npm is unavailable) was replaced with a temp-variable + ${_npm_prefix:+${_npm_prefix}/bin:} parameter expansion that safely contributes nothing when the prefix is empty.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

💥 Automated smoke test review - all systems nominal!

💥 [THE END] — Illustrated by Smoke Claude

@@ -0,0 +1,4 @@
---
"gh-aw": patch
Copy link
Contributor

Choose a reason for hiding this comment

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

Good use of a changeset file for this patch. The description clearly explains the PATH setup change for npm-installed Codex binary on self-hosted runners.

# shellcheck disable=SC1003
sudo -E awf --env-all --container-workdir "${GITHUB_WORKSPACE}" --allow-domains "172.30.0.1,api.openai.com,api.snapcraft.io,archive.ubuntu.com,azure.archive.ubuntu.com,crl.geotrust.com,crl.globalsign.com,crl.identrust.com,crl.sectigo.com,crl.thawte.com,crl.usertrust.com,crl.verisign.com,crl3.digicert.com,crl4.digicert.com,crls.ssl.com,host.docker.internal,json-schema.org,json.schemastore.org,keyserver.ubuntu.com,ocsp.digicert.com,ocsp.geotrust.com,ocsp.globalsign.com,ocsp.identrust.com,ocsp.sectigo.com,ocsp.ssl.com,ocsp.thawte.com,ocsp.usertrust.com,ocsp.verisign.com,openai.com,packagecloud.io,packages.cloud.google.com,packages.microsoft.com,ppa.launchpad.net,s.symcb.com,s.symcd.com,security.ubuntu.com,ts-crl.ws.symantec.com,ts-ocsp.ws.symantec.com" --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --enable-host-access --image-tag 0.23.0 --skip-pull --enable-api-proxy \
-- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && INSTRUCTION="$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" && codex ${GH_AW_MODEL_AGENT_CODEX:+-c model="$GH_AW_MODEL_AGENT_CODEX" }exec --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check "$INSTRUCTION"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
-- /bin/bash -c 'export PATH="$(npm config get prefix 2>/dev/null)/bin:$(find /opt/hostedtoolcache -maxdepth 4 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && INSTRUCTION="$(cat /tmp/gh-aw/aw-prompts/prompt.txt)" && codex ${GH_AW_MODEL_AGENT_CODEX:+-c model="$GH_AW_MODEL_AGENT_CODEX" }exec --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check "$INSTRUCTION"' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log
Copy link
Contributor

Choose a reason for hiding this comment

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

The PATH setup change from find /opt/hostedtoolcache to prepending $(npm config get prefix 2>/dev/null)/bin: looks correct. This ensures npm-installed binaries take priority over vendored system binaries on self-hosted runners.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codex engine fails on self-hosted runner: vendored codex-x86_64-unknown-linux-musl binary does not support --dangerously-bypass-approvals-and-sandbox

3 participants