Improve error message for npm optional deps and platforms bug - #8490
Improve error message for npm optional deps and platforms bug#8490kanej wants to merge 4 commits into
Conversation
🦋 Changeset detectedLatest commit: 22667f6 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
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 |
There was a problem hiding this comment.
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
HHE27while 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. |
| To fix it, update npm and reinstall your dependencies: | ||
|
|
||
| npm install --global npm@latest | ||
| rm -rf node_modules package-lock.json | ||
| npm install |
There was a problem hiding this comment.
No, we are fine here with some unix bias.
c4d6694 to
3c13902
Compare
There was a problem hiding this comment.
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
packageFromStackcurrently takes the lastnode_modules/<pkg>match across the entire stack (matches.at(-1)). In real stack traces the lastnode_modulesframe 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,
missingPackageis 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-msvcsuffix), so the renderedMissing 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}`,
};
There was a problem hiding this comment.
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
missingPackagesynthesized 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_REGEXonly 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;
3c13902 to
4b22c5a
Compare
3c13902 to
4b22c5a
Compare
4b22c5a to
62f35f6
Compare
There was a problem hiding this comment.
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
missingPackageas${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
missingPackageshape, but the real platform package names include an extra ABI/libc suffix on some platforms (e.g.-msvcon Windows,-gnu/-muslon 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-tracesshows the real failure, butprintErrorMessagesactually prints the originalerrorargument 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(
81e47aa to
9697c50
Compare
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.
9697c50 to
57fdc1d
Compare
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.
381e0b1 to
bc94df4
Compare
New unit tests to cover the detection, attempting to cover the encountered message shapes.
bc94df4 to
22667f6
Compare
Hardhat users have repeatedly hit a known npm bug (npm/cli#4828):
We currently work around this for
@nomicfoundation/edrby shipping all seven platform packages as regular dependencies instead ofoptionalDependencies, 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
Technical decisions
@nomicfoundation/edrawait importboundaries.EDRis already fully behind lazy dynamic imports, so each boundary could in principle be wrapped in a try/catch. We instead hook into the existinggetErrorWithCategoryfunction inerror-handler.ts. Central detection also catchessolidity-analyzer's failures. The one thing central detection does not cover is a consumer embeddinghardhatas a library and driving task actions directly, bypassingmain.ts/hhu.ts.@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-gnuwould otherwise misclassify as a Hardhat native-binding failure. Seenative-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 commitsfeat: detect and report failed native binding loads as HHE27is the feature: the descriptor,native-binding-error.ts, and theerror-handler.tswiring.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 withnpm, then manually remove the EDR and solidity-analyzer packages for the platform you're on: