TypeScript

Fix pnpm Ignored Build Scripts for Native Dependencies

Reproduce a missing SQLite binding, approve only its installer, and keep the same permission working in CI without enabling every dependency script.

Mahdi Ben RhoumaMahdi Ben Rhouma
9 min read

TL;DR#

When pnpm reports ignored build scripts, it has installed a dependency without authorising its installer. A native package can then fail because its compiled binding was never prepared. Check pnpm --version, review the named package, and approve that package alone. On the current CLI tested below, pnpm approve-builds better-sqlite3 saves the decision and rebuilds the pending dependency immediately. Commit the resulting project policy alongside the lockfile so CI receives it too.

The important version distinction is that current pnpm uses allowBuilds, or an existing permissions map. Advice that universally prescribes onlyBuiltDependencies belongs to older releases. The current approval documentation describes both current storage paths.

The install error and the missing native binding#

This behaviour was reproduced on 2 November 2026 using pnpm 12.16.1, Node.js 24.22.2 and better-sqlite3 12.4.1 in the official node:24-slim Docker image. The dependency version is deliberately pinned to one with an install script; it is not a recommendation to downgrade your application. The tagged package manifest shows what that script does.

The first install exited unsuccessfully after downloading and linking the packages:

text
Error: ERR_PNPM_IGNORED_BUILDS
 
  × installing dependencies
  ╰─▶ Ignored build scripts: better-sqlite3@12.4.1
  help: Run "pnpm approve-builds" to pick which dependencies should be allowed
        to run scripts.

Trying to open an in-memory database then failed:

text
Error: Could not locate the bindings file. Tried:

The complete error lists candidate locations for the native binding. That is useful evidence: the JavaScript package is present, but the native file it needs is absent. Installing the same package repeatedly does not answer whether its installer is permitted to run.

The source incident, pnpm issue #10260, concerns the opposite-looking symptom: an existing installation did not display the ignored-build warning after a package-manager upgrade. Its dependencies had already been built. That distinction explains why a working developer machine is weak evidence that a fresh CI installation has the right policy. The reproduction here tests a clean installation and the repair; it does not claim to reproduce every historical upgrade path in that issue.

Why the package exists but cannot open a database#

A dependency can contain JavaScript that loads a separate native library. In the pinned better-sqlite3 release, its install command first attempts to obtain a prebuilt binary, then falls back to a source build if that attempt fails:

sh
prebuild-install || node-gyp rebuild --release

That command comes from the package's own manifest. Blocking it can leave the JavaScript wrapper installed without a usable binding. Changing import syntax or adding TypeScript declarations cannot create that missing binary.

pnpm's build settings reference separates several decisions that are easy to conflate. The build policy decides which dependency installers are authorised. Strict dependency builds decides whether unreviewed installers cause installation to fail. Ignore scripts disables lifecycle scripts more broadly. Turning a failure into a warning does not prepare the native library: the application still needs the package's actual installation work.

This also differs from a binary built for the wrong runtime or operating system. If your binding exists but names an incompatible environment, follow the diagnostic distinction in Node Sass could not find a binding for your environment. That article covers rebuilding under the Node that actually runs the application, and replacing obsolete Node Sass where its runtime support ends. Approval cannot make an unsupported native package support a newer runtime.

Fix the one package that needs its installer#

1. Identify the CLI and the blocked dependency#

Run these commands from the project root, or the workspace root in a monorepo:

sh
pnpm --version
node --version
pnpm ignored-builds

In the reproduction, the final command listed better-sqlite3 and suggested adding it to the allow map. Review the exact package and resolved version in your lockfile. A transitive dependency may be the component that needs permission, even when your application only imports the parent library.

Do not assume every package with a familiar name still uses the same installer. Package authors can change their distribution layout between releases. Inspect the manifest for the resolved release rather than copying a list of native packages from another project.

2. Approve the reviewed package#

On the current CLI used for this reproduction:

sh
pnpm approve-builds better-sqlite3

Substitute the actual blocked package after reviewing it. The approval command reference documents positional package names and says pending approved packages are rebuilt immediately. You can also invoke the command without package arguments for its interactive selection interface. Select the dependency you reviewed rather than accepting every pending installer.

The tested command ran the better-sqlite3 install hook and completed successfully. It did not require a separate rebuild afterwards. Running a rebuild before approval was also tested as a negative control: it failed with the same ignored-build error and the database still could not open. A rebuild is an execution request, not a grant of permission.

3. Inspect the saved policy#

With no existing permissions map, the tested CLI created this project file:

yaml
allowBuilds:
  better-sqlite3: true

Merge the relevant entry into an existing workspace file; do not replace its package patterns, catalogues or other settings. Keep the file at the workspace root, where everyone installs the project. A single-package project can also use this file to store its policy.

The separate reproduction started with an empty permissions map. Approval wrote the equivalent decision there:

yaml
permissions:
  better-sqlite3:
    build: true

The permissions documentation explains that a package's build decision there takes precedence over its allowBuilds entry. Check the effective entry when an apparently correct allow map still leaves a package blocked. Avoid maintaining contradictory decisions in both places.

4. Commit the project decision#

Include the policy file with the package manifest and pnpm lockfile in your normal change review. The approval is project configuration, not a one-off terminal preference. CI cannot inherit a file that remains only on your laptop.

Do not replace the narrow approval with a global permission or the setting that allows all dependency builds. The build settings documentation explains that the broad setting covers dependency installers across the graph, including future changes. Fixing one SQLite dependency does not require granting that scope.

Verify the binding and a fresh installation#

A passing install is useful, but opening the native module tests the operation that originally failed. For the pinned reproduction, save this complete file:

js
const Database = require('better-sqlite3');
 
const db = new Database(':memory:');
const result = db.prepare('SELECT 42 AS answer').get();
console.log(JSON.stringify(result));
db.close();

Run it directly with the application's Node runtime:

sh
node verify.cjs

After approval, the observed output was:

text
{"answer":42}

For another package, exercise its native operation: processing an image, opening its database or starting its binary. Merely importing a wrapper can miss libraries that load the native code lazily.

Then verify a new checkout or a disposable CI job using the committed manifest, lockfile and policy:

sh
pnpm install --frozen-lockfile
node verify.cjs

The reproduction repeated installation in a separate empty directory with those files and a separate empty pnpm store. The package's installer ran, installation succeeded, and the same query returned the same answer. This checks that the policy works independently of the original directory's built output. It also keeps the lockfile fixed while testing permissions; deleting a lockfile would change dependency resolution and make the result harder to interpret.

Older approval advice and existing build output#

Choose configuration for the CLI your project actually runs. The pnpm 10 approval documentation describes approved packages in onlyBuiltDependencies and unapproved packages in ignoredBuiltDependencies. The current migration note says those legacy settings were removed in pnpm 11 in favour of the allow map. Current documentation also describes the additional permissions representation. An older answer can be correct for its release and ineffective for yours.

This is why checking the version comes before editing YAML. Keep the package-manager version used locally aligned with CI, and review a policy migration as configuration work. Do not assume a successful install under an older manager proves that the same files authorise scripts under a newer one.

Existing build output adds another complication. In the linked issue, the maintainer explains that packages installed and built before the upgrade already had their build result. Missing warnings in that directory did not establish what would happen on a clean machine. Use a fresh checkout for this check instead of deleting shared package stores or other projects' dependencies.

If a Docker container still cannot see the package at all, inspect its mounts before approving scripts again. A bind mount can hide dependencies installed in the image, and a reused dependency volume can keep old contents. Cannot find module in Docker Compose covers those specific cases. That is a separate diagnosis from a present JavaScript package whose native installer was blocked.

If approval changes the failure into a compiler error, the permission repair has exposed the next installation problem. On macOS, an error naming missing Xcode or Command Line Tools belongs to the node-gyp CLT repair. Resolve the toolchain or supported binary availability for that package and runtime. Keep the explicit dependency approval in place, then verify the native operation again in the environment that will run the application.

Further reading: Fix pnpm ERR_PNPM_OUTDATED_LOCKFILE in CI.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.