Electron Builds Pass Locally but Fail on CI With a Missing Native Module
The desktop build worked on my machine and on every teammate machine, then failed on the build server with a module that supposedly could not be found. It was there the whole time, compiled for the wrong runtime.
Benjamin Fazli
Principal EngineerSkopje, North Macedonia

The symptom
npm run build produced a working installer locally. The same command on CI failed while packaging with a message claiming a native module could not be found, naming a file that was plainly sitting in node_modules. Deleting the cache did nothing. Pinning the dependency did nothing.
The actual problem
Electron ships its own build of Node, and it is almost never the same version as the Node that runs on your build server. Native modules are compiled against a specific ABI. A module built for Node 22 will not load inside an Electron runtime expecting a different ABI, and the error you get is a resolution failure rather than a version complaint, which sends everyone hunting in the wrong direction.
It worked locally because I had run the app in development weeks earlier, which had quietly rebuilt the module for Electron and left the result in my node_modules. CI started from a clean install every time and never performed that step.
The fix
Rebuild native modules against the Electron ABI as an explicit step, so it happens on every machine rather than by accident:
{
"scripts": {
"postinstall": "electron-builder install-app-deps",
"build": "electron-builder --publish never"
}
}install-app-deps reads the Electron version from your dependencies and rebuilds anything native against the matching ABI. Because it runs as postinstall, CI performs it as part of the clean install and the packaging step finds exactly what it expects.
Two supporting changes made the pipeline predictable:
- Pin the Node version on CI with a
.nvmrcfile and have the workflow read it. Matching your local major version removes an entire class of difference. - Cache
~/.cache/electronand~/.cache/electron-builderrather thannode_modules. Cachingnode_modulesis what preserved my accidental local rebuild in the first place, and it will hide this bug again the moment you restore it.
Proving it locally
The reason this took a day rather than an hour is that I kept testing on a machine that could not reproduce the failure. Once I started with a genuinely clean tree, the loop tightened immediately:
rm -rf node_modules
npm ci
npm run buildIf a build only fails on CI, the difference is almost never CI. It is the state your machine has accumulated. Reproduce on a clean tree before you touch the pipeline configuration.