fix(resolver): actionable "module not found" + host-mounted node_modules symlink coverage#114
Merged
Merged
Conversation
railway-app
Bot
temporarily deployed
to
secure-exec / secure-exec-pr-114
June 23, 2026 00:01
Destroyed
railway-app
Bot
temporarily deployed
to
rivet-frontend / secure-exec-pr-114
June 23, 2026 00:01
Destroyed
|
🚅 Deployed to the secure-exec-pr-114 environment in rivet-frontend
🚅 Deployed to the secure-exec-pr-114 environment in secure-exec
|
…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
force-pushed
the
fix/resolver-not-found-diagnostics
branch
from
June 24, 2026 08:22
fc204e1 to
e3de41e
Compare
railway-app
Bot
temporarily deployed
to
rivet-frontend / secure-exec-pr-114
June 24, 2026 08:22
Destroyed
railway-app
Bot
temporarily deployed
to
secure-exec / secure-exec-pr-114
June 24, 2026 08:22
Destroyed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Follow-up to #109 (
NodeRuntime nodeModulesresolution). Closes the gap a user hit on0.3.1-rc.2: the JS SDKNodeRuntime.create({ nodeModules })path still surfaced_resolveModule returned non-string for '<pkg>'.Two things were missing on
main:_resolveModule returned non-stringinto a real "module not found" message was authored but unmerged, so users still saw the internal-type-error-looking message.node_modulesof real package directories. Real installs are not plain: pnpm/yarn laynode_modulesout as symlinks into a.pnpmstore, and that symlinked layout is what the TypeScriptnodeModulesoption actually produces. It was never tested end-to-end.What this does
crates/v8-runtime/src/execution.rs): name the importer, state it as not-found with remediation. Also call out the host-mountednode_modulescase explicitly: ahost_dirmount confines reads to its root (anchoredopenat2(RESOLVE_BENEATH)), so a package symlinked out of the mounted tree (pnpm/yarn workspace orfile: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 leafnode_modules.crates/sidecar/tests/node_modules_symlink_resolution.rs): boots a real VM, projects a pnpm-stylenode_modules(top-level relative symlinks into.pnpm, plus a scoped package) at guest/tmp/node_modulesvia the samehost_dirmount the TSnodeModulesoption emits, and asserts both a plain and a scoped symlinked package resolve from guestimport.crates/execution/tests/module_resolution.rs): a bare package whosepackage.jsonhasmainbut noexportsmap (separate code path fromexports).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 guestimport.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).[email protected]: plain, transitive, CJSrequire,createRequire, pnpm-symlink,exports-map, and scoped layouts all resolve; the only repros of the exact error are (a)hostPathpointed at the wrong directory and (b) a symlink escaping the mount root.Refs #109.