Skip to content

module: MODULE_NOT_FOUND / ERR_MODULE_NOT_FOUND error shape, ResolveMessage extends Error (+1 test) - #34845

Closed
cirospaciari wants to merge 2 commits into
mainfrom
claude/module-not-found-shape
Closed

module: MODULE_NOT_FOUND / ERR_MODULE_NOT_FOUND error shape, ResolveMessage extends Error (+1 test)#34845
cirospaciari wants to merge 2 commits into
mainfrom
claude/module-not-found-shape

Conversation

@cirospaciari

Copy link
Copy Markdown
Member

The ubiquitous userland pattern err.code === 'MODULE_NOT_FOUND' never matched on bun: a failed require/import threw ResolveMessage with bun's own message and no code at all. This is real compat value beyond any test — error-handling code across the npm ecosystem branches on these codes.

Node's shapes (established on the v26.3.0 binary, not assumed)

Case code message
require('./nope') MODULE_NOT_FOUND Cannot find module './nope'\nRequire stack:\n- <parent> + requireStack: [<parent>]
import('nope/sub') ERR_MODULE_NOT_FOUND Cannot find package 'nope' imported from <parent> — specifier truncated to package name, @scope/name kept
require.resolve(x, {paths: [1]}) ERR_INVALID_ARG_TYPE The "paths" argument must be array of strings. Received an instance of Array

Bun now matches all three (verified byte-for-byte, including the truncation rules).

Approach: keep the class, make it node-shaped

ResolveMessage is documented public surface (Bun.build logs assert name/constructor), so replacing the thrown class would break bun's own API. Instead it now extends Error via a new prototypeBase option in the class codegen — the direction NodeUtilTypesModule.cpp's own FIXME sanctions ("delete this once ResolveMessage and BuildMessage extend Error"). The message getter returns node's exact text for runtime import kinds only, prefix-gated so invalid-URL/data-URL/ENAMETOOLONG texts and the CLI stderr display are untouched.

Bun-owned assertion changes — 9, listed for sign-off

  1. test/js/node/missing-module.test.js — 7 regexes updated from Cannot find package 'X' from to node's CJS/ESM texts.
  2. test/js/bun/resolve/resolve-error.test.ts — 1: import(":://filesystem") now yields Cannot find package '::', byte-identical to node.
  3. test/js/bun/resolve/import-meta.test.js — 1: relaxed a quoted-referrer assertion to toInclude(path) (node's Require-stack line is unquoted; intent — path-not-file-URL — preserved, passes on both binaries).

Honest gaps left

name/constructor stay "ResolveMessage" (documented bun API); ESM message uses the given specifier without a url prop; stack carries no frames (bun doesn't capture JS frames at resolution time); --require missing keeps bun's CLI error; pre-existing: require.resolve('nope', {paths}) auto-installs instead of throwing.

Of the 4 candidate tests, only test-require-resolve-invalid-paths can honestly convert (vendored, verbatim, 3 tampers detected, red on system bun). The others are product-blocked: test-require-mjs needs --no-experimental-require-module (bun's require(esm) is always on), test-directory-import asserts ERR_UNSUPPORTED_DIR_IMPORT while bun intentionally resolves directory imports.

Regressions — interleaved before/after on saved binaries

resolve/ 296 tests 0 fail; node/module 89/0; worker_threads 91/0; misc 53/0; 48 vendored require/module files 48/0; bundler sample identical single pre-existing fail both sides; all cli-run deltas proven load-flakes by interleaving. Bun.build logs unchanged except now instanceof Error.

A failed require or import threw ResolveMessage with bun's own message and no
code, so the ubiquitous userland pattern `err.code === 'MODULE_NOT_FOUND'`
never matched. ResolveMessage now extends Error via a new prototypeBase class
option in codegen -- the direction NodeUtilTypesModule.cpp's FIXME already
sanctions -- and its message getter returns node's exact text for runtime
import kinds: MODULE_NOT_FOUND with a Require stack and a requireStack array
for CJS, ERR_MODULE_NOT_FOUND with the specifier truncated to the package name
for bare ESM imports. The class itself is kept: Bun.build's log API documents
name and constructor as ResolveMessage, and the getter is prefix-gated so
invalid-URL, data-URL and ENAMETOOLONG texts are untouched, as is the CLI
stderr display text.

require.resolve now validates options.paths with node's exact
ERR_INVALID_ARG_TYPE and ERR_INVALID_ARG_VALUE messages.

Nine assertions across three bun-owned test files are updated to the node
shapes; each is listed in the pull request for sign-off.

Adds test-require-resolve-invalid-paths from Node v26.3.0, verbatim.
@robobun

robobun commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

@github-actions

Copy link
Copy Markdown
Contributor

Found 2 issues this PR may fix:

  1. BuildMessage/ResolveMessage should extend Error #7531 - ResolveMessage now extends Error via prototypeBase: "Error", so instanceof Error checks pass and error properties like .message/.stack work correctly
  2. Bun does not log correct error in vite when a module is missing #12510 - Vite now receives proper Error objects with .message, .stack, and .code for missing modules, fixing the undefined error display

If this is helpful, copy the block below into the PR description to auto-close these issues on merge.

Fixes #7531
Fixes #12510

🤖 Generated with Claude Code

@cirospaciari

Copy link
Copy Markdown
Member Author

Folded into #34660 (branch merged) as part of PR consolidation — same commits and tests, fewer PRs.

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.

2 participants