Skip to content

fix(resolver): actionable "module not found" + host-mounted node_modules symlink coverage#114

Merged
NathanFlurry merged 1 commit into
mainfrom
fix/resolver-not-found-diagnostics
Jun 24, 2026
Merged

fix(resolver): actionable "module not found" + host-mounted node_modules symlink coverage#114
NathanFlurry merged 1 commit into
mainfrom
fix/resolver-not-found-diagnostics

Conversation

@NathanFlurry

@NathanFlurry NathanFlurry commented Jun 23, 2026

Copy link
Copy Markdown
Member

Summary

Follow-up to #109 (NodeRuntime nodeModules resolution). Closes the gap a user hit on 0.3.1-rc.2: the JS SDK NodeRuntime.create({ nodeModules }) path still surfaced _resolveModule returned non-string for '<pkg>'.

Two things were missing on main:

  1. The actionable error never merged. The reword that turns the cryptic _resolveModule returned non-string into a real "module not found" message was authored but unmerged, so users still saw the internal-type-error-looking message.
  2. The regression test only covered a plain layout. test+docs: host-mounted node_modules resolution (#109) #113 projects a host node_modules of real package directories. Real installs are not plain: pnpm/yarn lay node_modules out as symlinks into a .pnpm store, and that symlinked layout is what the TypeScript nodeModules option actually produces. It was never tested end-to-end.

What this does

  • Reword the not-found error (crates/v8-runtime/src/execution.rs): name the importer, state it as not-found with remediation. Also call out the host-mounted node_modules case explicitly: a host_dir mount confines reads to its root (anchored openat2(RESOLVE_BENEATH)), so a package symlinked out of the mounted tree (pnpm/yarn workspace or file: deps linking to the workspace root or an external store) is not followed and surfaces as not-found. Remediation: mount a directory that contains every symlink target (e.g. the workspace root), not the leaf node_modules.
  • End-to-end symlink regression (crates/sidecar/tests/node_modules_symlink_resolution.rs): boots a real VM, projects a pnpm-style node_modules (top-level relative symlinks into .pnpm, plus a scoped package) at guest /tmp/node_modules via the same host_dir mount the TS nodeModules option emits, and asserts both a plain and a scoped symlinked package resolve from guest import.
  • Main-only ESM test (crates/execution/tests/module_resolution.rs): a bare package whose package.json has main but no exports map (separate code path from exports).
  • Docs (features/module-loading): note the escaping-symlink confinement and the mount-the-workspace-root fix.

Not changed (by design)

The mount still confines reads to its root. We do not follow symlinks that escape the mounted tree: a malicious package installed by the client could plant a symlink to an arbitrary host path, and the read-only mount must not become a host-fs read primitive for guest code. The correct fix for workspace layouts is a wider mount root, which the docs and error now state.

Verification

  • cargo test -p secure-exec-sidecar --test node_modules_symlink_resolution → pnpm-symlinked plain + scoped packages resolve from guest import.
  • cargo test -p secure-exec-execution --test module_resolution → 37 passed (incl. faithful_pnpm_symlink_layout_resolves_via_realpath_walk, symlinked_package_escape_is_not_resolved).
  • Reproduced the user's path against published [email protected]: plain, transitive, CJS require, createRequire, pnpm-symlink, exports-map, and scoped layouts all resolve; the only repros of the exact error are (a) hostPath pointed at the wrong directory and (b) a symlink escaping the mount root.

Refs #109.

@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-114 June 23, 2026 00:01 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-114 June 23, 2026 00:01 Destroyed
@railway-app

railway-app Bot commented Jun 23, 2026

Copy link
Copy Markdown

🚅 Deployed to the secure-exec-pr-114 environment in rivet-frontend

Service Status Web Updated (UTC)
secure-exec 😴 Sleeping (View Logs) Jun 24, 2026 at 8:33 am

🚅 Deployed to the secure-exec-pr-114 environment in secure-exec

Service Status Web Updated (UTC)
secure-exec 😴 Sleeping (View Logs) Web Jun 24, 2026 at 8:28 am

…les symlink coverage

When the host module resolver returns null for a bare specifier, the V8
runtime threw `_resolveModule returned non-string for '<spec>'`. That reads
like an internal type error, but it actually means the module could not be
located, almost always a node_modules layout/discovery problem.

Reword the message to name the importer and state it as a not-found error
with concrete remediation. Also call out the host-mounted node_modules case
(what `NodeRuntime.create({ nodeModules })` projects): a host_dir mount
confines reads to its root, so a package symlinked OUT of the mounted tree
(pnpm/yarn workspace or `file:` deps that link to the workspace root or an
external store) is not followed and surfaces here as not-found. The fix for
that case is to mount a directory containing every symlink target (e.g. the
workspace root), not to widen the mount.

Regression coverage:
- crates/execution/tests/module_resolution.rs: a bare ESM package whose
  package.json has `main` but no `exports` map (the `main` fallback is a
  separate path from `exports`).
- crates/sidecar/tests/node_modules_symlink_resolution.rs: end-to-end through
  a real VM + host_dir mount, projecting a pnpm-style node_modules (top-level
  relative symlinks into a `.pnpm` store, plus a scoped package) at guest
  /tmp/node_modules and asserting both resolve from guest `import`. The
  sibling #113 test only covers a plain layout; this locks in the symlinked
  layout that the TypeScript `nodeModules` option actually produces.

Docs: note the escaping-symlink confinement in features/module-loading.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@NathanFlurry
NathanFlurry force-pushed the fix/resolver-not-found-diagnostics branch from fc204e1 to e3de41e Compare June 24, 2026 08:22
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-114 June 24, 2026 08:22 Destroyed
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-114 June 24, 2026 08:22 Destroyed
@NathanFlurry NathanFlurry changed the title fix(resolver): actionable "module not found" error + main-only ESM regression test fix(resolver): actionable "module not found" + host-mounted node_modules symlink coverage Jun 24, 2026
@NathanFlurry
NathanFlurry merged commit fc26525 into main Jun 24, 2026
4 checks passed
@NathanFlurry
NathanFlurry deleted the fix/resolver-not-found-diagnostics branch June 24, 2026 22:23
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