Skip to content

Improve error message for npm optional deps and platforms bug - #8490

Open
kanej wants to merge 4 commits into
mainfrom
improve-error-npm-optional-deps-and-platforms-bug
Open

Improve error message for npm optional deps and platforms bug#8490
kanej wants to merge 4 commits into
mainfrom
improve-error-npm-optional-deps-and-platforms-bug

Conversation

@kanej

@kanej kanej commented Aug 5, 2026

Copy link
Copy Markdown
Member

Hardhat users have repeatedly hit a known npm bug (npm/cli#4828):

npm versions before 11.3.0 rewrite package-lock.json from the node_modules on whichever machine last ran install, and in doing so drop the other platforms' optional-dependency entries. The next install on one of those platforms then has no lockfile entry telling it to fetch its native binary, so nothing gets installed — not the wrong platform's binary, none at all — and the loader throws a Cannot find module inside node_modules.

We currently work around this for @nomicfoundation/edr by shipping all seven platform packages as regular dependencies instead of optionalDependencies, which costs every install ~165-175MB. We want to roll that workaround back as the npm bug is resolved in later npm versions (>= 11.3.0), but Node 22 (Hardhat's minimum supported version) still bundles npm 10 by default, so some users would hit a broken install with no explanation.

This PR means we now detect a failed native-binding load in Hardhat's central error-printing path and report it as a HardhatError (HHE27) naming the missing package and how to fix it i.e. update to a more recent version of npm.

Resolves #8489.

Preview

$ npx hardhat compile
Error HHE27: Hardhat couldn't load the native binding for @nomicfoundation/edr.

Missing platform package: @nomicfoundation/edr-linux-arm64-gnu

This is usually caused by a known npm bug with optional dependencies (https://github.com/npm/cli/issues/4828), fixed in npm 11.3.0. Node.js 22 bundles npm 10, which doesn't include the fix.

To fix it, update npm and reinstall your dependencies:

  npm install --global npm@latest
  rm -rf node_modules package-lock.json
  npm install

If that command fails with EBADENGINE, update Node.js to its latest patch release first (or to a newer major), then retry.

For more info go to https://hardhat.org/HHE27 or run Hardhat with --show-stack-traces

Technical decisions

  • Central detection over wrapping the five @nomicfoundation/edr await import boundaries. EDR is already fully behind lazy dynamic imports, so each boundary could in principle be wrapped in a try/catch. We instead hook into the existing getErrorWithCategory function in error-handler.ts. Central detection also catches solidity-analyzer's failures. The one thing central detection does not cover is a consumer embedding hardhat as a library and driving task actions directly, bypassing main.ts/hhu.ts.
  • An explicit allowlist (@nomicfoundation/edr, @nomicfoundation/solidity-analyzer) rather than looser regex matching. A platform-suffix-shaped module name alone isn't enough, a user's own dependency named e.g. some-app-linux-x64-gnu would otherwise misclassify as a Hardhat native-binding failure. See native-binding-error.test.ts's allowlist-blocked cases.

Review

Tip

Review a commit at a time for easier review

  • refactor: move error-handler to cli/error-handling/moves the error handler in to a subdirectory, to give a natural place for additional error handling utils in the following commits
  • feat: detect and report failed native binding loads as HHE27 is the feature: the descriptor, native-binding-error.ts, and the error-handler.ts wiring.
  • test: cover detectNativeBindingFailure... is worth reading against the message shapes it's asserting on — those come from the two loaders' actual output, not synthesized.

Manual testing

Use verdaccio (after pnpm version-for-release) to do a local clean install with npm, then manually remove the EDR and solidity-analyzer packages for the platform you're on:

pnpm version-for-release
pnpm verdaccio start

# In a different terminal
pnpm verdaccio publish --no-git-checks
mkdir /tmp/example-hardhat-install && cd /tmp/example-hardhat-install

# Point npm/npx at the local registry for everything run from this directory -
# without this, npx would just fetch the real, unpatched hardhat from npmjs.org
echo "registry=http://127.0.0.1:4873" > .npmrc

# Run the install, I find giving the version just installed in verdaccio
# reduces your risk of using a cached version
npx hardhat@3.14.0 --init --template node-test-runner-viem

# Confirm working
npx hardhat test

# remove a different platform (windows), and everything continues to work
rm -rf node_modules/@nomicfoundation/edr-win32-x64-msvc
npx hardhat test

# remove your platform's EDR binary and see the new error
rm -rf node_modules/@nomicfoundation/edr-linux-arm64-gnu
npx hardhat test

# restore
npm install
npx hardhat test

# same thing, but for solidity-analyzer's binary
rm -rf node_modules/@nomicfoundation/solidity-analyzer-linux-arm64-gnu
npx hardhat test

# restore, then tear down verdaccio and the version-for-release changes
npm install
npx hardhat test
pnpm verdaccio stop
git restore .

Copilot AI review requested due to automatic review settings August 5, 2026 11:36
@changeset-bot

changeset-bot Bot commented Aug 5, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 22667f6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@nomicfoundation/hardhat-errors Minor
hardhat Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@kanej kanej added no docs needed This PR doesn't require links to documentation no peer bump needed labels Aug 5, 2026

Copilot AI 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.

Pull request overview

This PR improves Hardhat’s CLI error reporting for a known npm optional-dependency lockfile bug by detecting native-binding load failures (for @nomicfoundation/edr and @nomicfoundation/solidity-analyzer) and reclassifying them into a dedicated Hardhat error (HHE27) with actionable remediation steps.

Changes:

  • Added native-binding failure detection (detectNativeBindingFailure) with an explicit allowlist to avoid misclassification.
  • Wired the detection into the central CLI error handler to emit HHE27 while preserving the original error for --show-stack-traces.
  • Added descriptors + tests covering multiple real-world loader message shapes and regression behavior.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/hardhat/src/internal/cli/native-binding-error.ts Implements native-binding failure detection logic and stack-based fallback recovery.
packages/hardhat/src/internal/cli/error-handler.ts Routes detected native-binding load failures to HHE27 in the central error-printing path.
packages/hardhat-errors/src/descriptors.ts Adds the new NATIVE_BINDING_LOAD_FAILED (HHE27) descriptor and website copy.
packages/hardhat/test/internal/cli/native-binding-error.ts Adds unit tests for detection across multiple loader error shapes and guardrails.
packages/hardhat/test/internal/cli/error-handler.ts Adds regression tests ensuring categorization and stack trace behavior remain correct.
.changeset/native-binding-load-error.md Declares a minor release for the new user-facing error behavior.

Comment on lines +572 to +576
To fix it, update npm and reinstall your dependencies:

npm install --global npm@latest
rm -rf node_modules package-lock.json
npm install

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

No, we are fine here with some unix bias.

Comment thread packages/hardhat/src/internal/cli/error-handling/native-binding-error.ts Outdated
Copilot AI review requested due to automatic review settings August 5, 2026 11:42
@kanej
kanej force-pushed the improve-error-npm-optional-deps-and-platforms-bug branch from c4d6694 to 3c13902 Compare August 5, 2026 11:45

Copilot AI 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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (3)

packages/hardhat/src/internal/cli/native-binding-error.ts:126

  • packageFromStack currently takes the last node_modules/<pkg> match across the entire stack (matches.at(-1)). In real stack traces the last node_modules frame is often the outer caller (e.g. Hardhat) rather than the failing loader (e.g. @nomicfoundation/edr), which can make the fallback path miss native-binding failures entirely.

Instead, walk stack lines from top to bottom (innermost first) and return the last nested node_modules/<pkg> segment from the first line that contains any matches.

  // The last `node_modules/<pkg>` segment of the innermost frame is the package
  // that actually contains the failing loader.
  const matches = [...error.stack.matchAll(PACKAGE_IN_PATH_REGEX)];
  const last = matches.at(-1);

  return last === undefined ? undefined : last[1].replace(/\\/g, "/");

packages/hardhat-errors/src/descriptors.ts:576

  • The remediation snippet uses rm -rf ..., which is POSIX-specific and won't work on Windows (a supported platform). It would be more actionable to describe the deletion step in a platform-agnostic way (or include Windows-equivalent commands).
To fix it, update npm and reinstall your dependencies:

  npm install --global npm@latest
  rm -rf node_modules package-lock.json
  npm install

packages/hardhat/src/internal/cli/native-binding-error.ts:98

  • In the fallback path, missingPackage is synthesized as ${parentPackage}-${process.platform}-${process.arch}. For common platforms this doesn't match actual native platform package names (e.g. the repo lockfile only contains @nomicfoundation/edr-linux-arm64-{gnu|musl} and Windows packages typically have a -msvc suffix), so the rendered Missing platform package: line can point users at a package that doesn't exist.

Consider either (a) deriving a more accurate platform triple (e.g. add -msvc on win32 and detect -gnu vs -musl on linux), or (b) changing the error template/params so the fallback case can avoid asserting a specific package name when it can't be known.

      return {
        parentPackage,
        missingPackage: `${parentPackage}-${process.platform}-${process.arch}`,
      };

Copilot AI review requested due to automatic review settings August 5, 2026 11:47
@kanej kanej changed the title Improve error npm optional deps and platforms bug Improve error message for npm optional deps and platforms bug Aug 5, 2026

Copilot AI 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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (3)

packages/hardhat-errors/src/descriptors.ts:576

  • The remediation steps include rm -rf, which doesn’t work on Windows (a supported platform). This risks making HHE27 less actionable for Windows users; consider using platform-neutral wording (e.g. “delete node_modules and package-lock.json”) or providing a Windows alternative.
To fix it, update npm and reinstall your dependencies:

  npm install --global npm@latest
  rm -rf node_modules package-lock.json
  npm install

packages/hardhat/src/internal/cli/native-binding-error.ts:98

  • The fallback missingPackage synthesized from ${process.platform}-${process.arch} does not match actual napi-rs platform package names in common cases (e.g. Linux packages are typically suffixed with -gnu/-musl, Windows with -msvc). This can cause HHE27 to recommend a non-existent package name. Using a placeholder like <platform-triple> (or computing the real napi platform triple) would avoid misleading remediation.
      return {
        parentPackage,
        missingPackage: `${parentPackage}-${process.platform}-${process.arch}`,
      };

packages/hardhat/src/internal/cli/native-binding-error.ts:27

  • MISSING_MODULE_REGEX only matches module names wrapped in single quotes. Node’s MODULE_NOT_FOUND messages can also use double quotes depending on context/platform, which would make native-binding failures go undetected and fall back to the generic error path. Consider accepting both quote styles.

This issue also appears on line 95 of the same file.

const MISSING_MODULE_REGEX = /Cannot find (?:module|package) '([^']+)'/g;

@kanej
kanej force-pushed the improve-error-npm-optional-deps-and-platforms-bug branch from 3c13902 to 4b22c5a Compare August 5, 2026 15:20
@kanej
kanej requested review from Copilot August 5, 2026 15:20
@kanej
kanej force-pushed the improve-error-npm-optional-deps-and-platforms-bug branch from 3c13902 to 4b22c5a Compare August 5, 2026 15:20
@kanej
kanej force-pushed the improve-error-npm-optional-deps-and-platforms-bug branch from 4b22c5a to 62f35f6 Compare August 5, 2026 15:23

Copilot AI 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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

Suppressed comments (4)

packages/hardhat-errors/src/descriptors.ts:576

  • The remediation snippet uses rm -rf, which won't work on Windows. Since this is a user-facing error message, it would be better to use platform-neutral wording (or include a Windows equivalent) so Windows users can follow the instructions.
To fix it, update npm and reinstall your dependencies:

  npm install --global npm@latest
  rm -rf node_modules package-lock.json
  npm install

packages/hardhat/src/internal/cli/native-binding-error.ts:99

  • The fallback path constructs missingPackage as ${parentPackage}-${process.platform}-${process.arch}, but the actual platform packages include an ABI/libc suffix on some platforms (e.g. -win32-x64-msvc, -linux-x64-gnu/-musl). As a result, this can report a package name that doesn't exist, which is misleading in the HHE27 message.
      return {
        parentPackage,
        missingPackage: `${parentPackage}-${process.platform}-${process.arch}`,
      };
    }

packages/hardhat/test/internal/cli/native-binding-error.ts:90

  • This test hardcodes the fallback missingPackage shape, but the real platform package names include an extra ABI/libc suffix on some platforms (e.g. -msvc on Windows, -gnu/-musl on Linux). If the fallback logic is corrected to return an installable package name, the assertion here should be updated accordingly.
    it("falls back to the stack frames when the loader gives up without naming a module", () => {
      const error = errorWithStack(
        "Cannot find native binding",
        [
          "Error: Cannot find native binding",
          "    at Object.<anonymous> (/project/node_modules/@nomicfoundation/edr/index.js:1:1)",
        ].join("\n"),
      );

      assert.deepEqual(detectNativeBindingFailure(error), {
        parentPackage: "@nomicfoundation/edr",
        missingPackage: `@nomicfoundation/edr-${process.platform}-${process.arch}`,
      });
    });

packages/hardhat/src/internal/cli/error-handler.ts:174

  • This comment says the original error is kept as the HardhatError cause so --show-stack-traces shows the real failure, but printErrorMessages actually prints the original error argument when stack traces are enabled. The behavior is fine, but the comment is currently misleading about why the real failure is shown.
  const nativeBindingFailure = detectNativeBindingFailure(error);
  if (nativeBindingFailure !== undefined) {
    return {
      category: ErrorCategory.HARDHAT,
      categorizedError: new HardhatError(

@kanej
kanej force-pushed the improve-error-npm-optional-deps-and-platforms-bug branch 4 times, most recently from 81e47aa to 9697c50 Compare August 7, 2026 10:46
Move error-handler.ts into its own directory as we are going to
add further error handling utils and files.

This is a mechanical refactor.
@kanej
kanej force-pushed the improve-error-npm-optional-deps-and-platforms-bug branch from 9697c50 to 57fdc1d Compare August 7, 2026 11:00
An npm bug in older versions of npm can mean that installing from a
valid package-lock.json can still not have the right optional
dependencies for your platform. This affects EDR and
`solidity-analyzer`.

We now detect this centrally in `getErrorWithCategory`. We do this in
`detectNativeBindingFailure` by walking the error's cause chain for a
missing module name with a napi platform triple (both CJS and ESM
message formats). We filter down to the two Hardhat packages explicitly
i.e. EDR and solidity analyzer.

A detected failure is recategorized as a new HardhatError, HHE27,
explaining the cause and the fix, with the original error kept as the
cause so --show-stack-traces still shows the real failure.

Resolves #8489.
@kanej
kanej force-pushed the improve-error-npm-optional-deps-and-platforms-bug branch 2 times, most recently from 381e0b1 to bc94df4 Compare August 7, 2026 12:23
kanej added 2 commits August 7, 2026 12:35
New unit tests to cover the detection, attempting to cover the
encountered message shapes.
@kanej
kanej force-pushed the improve-error-npm-optional-deps-and-platforms-bug branch from bc94df4 to 22667f6 Compare August 7, 2026 12:36
@kanej
kanej marked this pull request as ready for review August 7, 2026 13:18
@kanej
kanej requested a review from alcuadrado August 7, 2026 13:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

no docs needed This PR doesn't require links to documentation no peer bump needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error on platform clashes in package-lock.json

2 participants