Skip to content

test+docs: host-mounted node_modules resolution (#109)#113

Merged
NathanFlurry merged 1 commit into
mainfrom
issue-109-nodemodules-regression
Jun 23, 2026
Merged

test+docs: host-mounted node_modules resolution (#109)#113
NathanFlurry merged 1 commit into
mainfrom
issue-109-nodemodules-regression

Conversation

@NathanFlurry

@NathanFlurry NathanFlurry commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

Closes #109 (NodeRuntime nodeModules does not resolve external packages in v0.3.0).

Diagnosis: the guest error _resolveModule returned non-string for '<pkg>' is what the V8 side reports whenever the native resolver returns null, i.e. the package was not found through the mounted node_modules. This was a real bug in the published 0.3.0 filesystem/mount-read path. It is already fixed on main by the VFS refactor (the entire crates/vfs + crates/secure-exec-vfs and the reworked resolver landed after v0.3.0; the host_dir plugin itself is unchanged). Verified against a current-main build: plain packages, CJS require, exports maps, pnpm symlinks, scoped packages, and subpath exports all resolve.

No code fix is needed on main, so this PR is the regression test that would have caught the 0.3.0 break.

What the test does

crates/sidecar/tests/node_modules_host_mount_resolution.rs mirrors NodeRuntime.create({ nodeModules }) end-to-end over the wire:

  • seeds a host node_modules/mypkg (ESM),
  • mounts it at guest /tmp/node_modules via the host_dir mount plugin (read-only),
  • writes the guest program under /tmp (so the ancestor-node_modules walk reaches the mount),
  • executes import greet from "mypkg" and asserts it resolves and runs.

Lives in its own integration-test binary (one isolate per process), matching the existing guest-executing test convention.

Validation

Docs

The #109 reporter asked whether the behavior was expected/documented. Clarified that nodeModules is a convenience for a read-only host-directory mount (same machinery as mounts, defaulting to guest /tmp/node_modules):

  • features/module-loading.mdx: made the mount relationship explicit.
  • features/filesystem.mdx: added an "at a glance" bullet (it previously omitted nodeModules entirely).

pnpm --dir website build passes.

@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-113 June 22, 2026 21:16 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-113 June 22, 2026 21:17 Destroyed
@railway-app

railway-app Bot commented Jun 22, 2026

Copy link
Copy Markdown

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

Service Status Web Updated (UTC)
secure-exec 😴 Sleeping (View Logs) Jun 22, 2026 at 9:26 pm

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

Service Status Web Updated (UTC)
secure-exec 😴 Sleeping (View Logs) Web Jun 23, 2026 at 12:29 am

Add a sidecar integration test that mirrors NodeRuntime.create({ nodeModules }):
project a host node_modules at guest /tmp/node_modules via the host_dir mount
plugin, run a guest program under /tmp, and assert a bare import resolves the
projected package. A missing package reproduces the exact 0.3.0 symptom
('_resolveModule returned non-string'); a present one resolves and runs.

Package resolution through a host-mounted node_modules was broken in 0.3.0 and
is fixed on main by the VFS refactor; this locks the end-to-end behavior in.

Docs: make explicit that 'nodeModules' is a convenience for a read-only
host-directory mount (the same machinery as 'mounts', defaulting to guest
/tmp/node_modules). State it in features/module-loading and add it to the
features/filesystem 'at a glance' list, which previously omitted it.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@NathanFlurry
NathanFlurry force-pushed the issue-109-nodemodules-regression branch from 8ce8bb0 to ef86eb9 Compare June 23, 2026 01:19
@railway-app
railway-app Bot temporarily deployed to secure-exec / secure-exec-pr-113 June 23, 2026 01:19 Destroyed
@railway-app
railway-app Bot temporarily deployed to rivet-frontend / secure-exec-pr-113 June 23, 2026 01:19 Destroyed
@NathanFlurry NathanFlurry changed the title test(sidecar): regression for host-mounted node_modules resolution (#109) test+docs: host-mounted node_modules resolution (#109) Jun 23, 2026
@NathanFlurry
NathanFlurry merged commit 3604a03 into main Jun 23, 2026
3 of 4 checks passed
NathanFlurry added a commit that referenced this pull request Jun 24, 2026
…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 added a commit that referenced this pull request Jun 24, 2026
…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]>
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.

NodeRuntime nodeModules does not resolve external packages in v0.3.0

1 participant