npm 12 Won't Error — It Ships You Broken Installs
The failure mode is a green CI log. When npm 12 skips a postinstall, nothing throws. The install completes. The missing native binary or downloaded dependency surfaces at runtime instead, often in production. The checks your pipeline trusts pass anyway.
Who breaks
Three categories. Native compilation (node-gyp, sharp, better-sqlite3). Binary fetchers (esbuild, swc, prisma). Dev-tooling setup (husky). Anything that hangs work on a lifecycle hook now no-ops silently. Go through 11.18.0 before 12. That release carries the migration warnings and the tooling. Skip it and you lose the inventory step.
Why it's right anyway
Postinstall scripts have been the primary supply-chain vector for years. The weekend jscrambler was compromised, Socket flagged it in six minutes. Scripts-off-by-default is the systemic answer, and it lines up with the week's other toolchain moves. TypeScript 7.0's Go rewrite ships 10x compile speed without the programmatic API that ts-morph, ts-jest, and custom transformers call, so it can't land in real setups yet. Bun's $165k AI-assisted Zig→Rust rewrite shipped as 1.4, and it worked because one of the few engineers able to architecturally verify a runtime directed it. The pattern is stability and security over velocity.
An install that succeeds but does nothing is more dangerous than one that fails loudly — npm 12 makes that your default failure mode.
Treat this as a supply-chain project, not a version bump. Inventory the affected packages first. Then decide which ones get allow-listed back on.
What to do
Run `npm query ':attr(scripts,[postinstall])'` against your lockfile this sprint to inventory every dependency relying on install hooks before touching npm 12
Upgrade to npm 11.18.0 as a staging step, run the full pipeline, then schedule npm 12 as a separately tracked migration
Pin TypeScript to 6.x in the lockfile with a comment until ts-morph and ts-jest confirm 7.0 support