Skip to content

Fix Dax typecheck on providers that ship Node 20 - #430

Open
thundergolfer wants to merge 1 commit into
computesdk:masterfrom
thundergolfer:fix/dax-upgrade-stale-nodejs
Open

thundergolfer wants to merge 1 commit into
computesdk:masterfrom
thundergolfer:fix/dax-upgrade-stale-nodejs

Conversation

@thundergolfer

@thundergolfer thundergolfer commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Modal's Dax run was failing at typecheck with Cannot run turbo.json because @computesdk/modal defaults to node:20 and the benchmark skipped installing the pinned Node whenever any node was already on PATH.
  • OpenCode's install scripts pull a recent node-gyp/undici that needs Node 22+ (util.markAsUncloneable). On Node 20, bun install does not write node_modules/.bin, so bun turbo typecheck resolves to turbo.json.
  • Prepare now installs the pinned Node (24.14.1) when Node is missing or older than 22. Sandboxes that already ship Node 22+ (e.g. Vercel) are left alone.

Verified on a Modal V2 sandbox with the default node:20 image: Node is upgraded to v24.14.1 and all 7 Dax phases complete (bun turbo typecheck succeeds).

Test plan

  • Reproduced the published Modal failure (node:20 image → typecheck fails on turbo.json, node_modules/.bin/turbo missing)
  • Confirmed the same workload passes on node:24
  • Confirmed this script change on Modal's default node:20 image upgrades to v24.14.1 and finishes typecheck
  • Next scheduled / manually dispatched sandbox-dax run lists Modal as 7/7

Made with Cursor


Devin Review

OpenCode's native-module install needs Node 22+, so replace a stale
preinstalled Node instead of skipping the pin and failing at turbo.

Co-authored-by: Cursor <cursoragent@cursor.com>
@open-cla

open-cla Bot commented Sep 14, 2026

Copy link
Copy Markdown

Contributor License Agreement

The following contributors need CLA coverage:

Review and sign the CLA

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Devin Review found 3 potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment on lines +175 to 178
if [[ "$need_node" -eq 1 ]]; then
local archive="node-v${NODE_VERSION}-${NODE_ARCH}.tar.gz"
local prefix="/opt/node-v${NODE_VERSION}-${NODE_ARCH}"
if ! curl -fsSL "https://nodejs.org/download/release/v${NODE_VERSION}/${archive}" -o "/tmp/${archive}"; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Alpine upgrades install unusable Node

On musl with Node below 22, need_node downloads the glibc Node archive. The installed binary cannot start, so Alpine benchmarks fail.

Learn more

The script explicitly detects musl because standard Linux binaries can depend on glibc, which Alpine does not provide. The new version gate sends every pre-22 Node installation through the official Node archive. That archive uses glibc, unlike the musl-specific Bun archive selected by BUN_MUSL_SUFFIX. Extraction succeeds, but invoking the installed node fails because its dynamic loader is unavailable.

Example: An x86_64 node:20-alpine sandbox reports Node 20. need_node downloads node-v24.14.1-linux-x64.tar.gz, links it into /usr/local/bin, and preparation appears successful. The later node --version invocation cannot execute the binary, instead of reporting Node 24.

Recommended fix: On musl, install a Node 22+ musl build through Alpine's package repositories or another verified source. If no compatible release exists, emit a specific preparation error rather than installing the glibc archive. Validate the resulting node --version before completing prepare.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +175 to 178
if [[ "$need_node" -eq 1 ]]; then
local archive="node-v${NODE_VERSION}-${NODE_ARCH}.tar.gz"
local prefix="/opt/node-v${NODE_VERSION}-${NODE_ARCH}"
if ! curl -fsSL "https://nodejs.org/download/release/v${NODE_VERSION}/${archive}" -o "/tmp/${archive}"; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Node override bypasses required minimum

With BENCH_NODE_VERSION below 22, need_node installs that release without validation. OpenCode then runs on the unsupported version and can fail typecheck.

Learn more

BENCH_NODE_VERSION controls the archive selected by NODE_VERSION, while the new gate assumes any installed archive satisfies the Node 22 minimum. Nothing checks that assumption after extraction. A caller can therefore request an older release and receive a successful prepare phase even though the workload's stated runtime requirement remains unmet.

Example: With BENCH_NODE_VERSION=20.20.0 and no existing Node, need_node downloads and links Node 20.20.0. command -v node passes, but OpenCode still runs below Node 22 and can fail during dependency installation or typecheck.

Recommended fix: Parse and reject NODE_VERSION values below 22 before downloading, or verify the effective node --version after installation and fail preparation with a specific minimum-version error.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

for executable in node npm npx corepack; do
"${SUDO[@]}" ln -sfn "$prefix/bin/$executable" "/usr/local/bin/$executable"
done
hash -r 2>/dev/null || true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 PATH precedence defeats Node upgrade

When old Node precedes /usr/local/bin, hash -r resolves the old executable again. The benchmark remains below Node 22 after installing the replacement.

Learn more

Bash's hash -r only clears cached command locations. The next lookup still scans directories in their existing PATH order. If a provider prepends its bundled Node directory, linking the replacement under /usr/local/bin does not change which binary runs.

Example: A sandbox has PATH=/opt/provider/node/bin:/usr/local/bin:/usr/bin and Node 20 at /opt/provider/node/bin/node. Preparation installs Node 24 under /opt and links it into /usr/local/bin. After hash -r, node --version still resolves through /opt/provider/node/bin and reports Node 20.

Recommended fix: Prepend the installed prefix's bin directory to PATH, or invoke and export that absolute toolchain path. Then verify the effective node --version is at least 22 before returning from prepare.

Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

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.

1 participant