The Error
You run npm install or npm rebuild and the build blows up with:
gyp info spawn args '-Wno-unused-variable',
gyp info spawn args '-arch', 'arm64'
clang: error: linker command failed with exit code 1 (use -v to see invocation)
gyp ERR! build error
gyp ERR! stack Error: `make` failed with exit code: 2
Native Node.js modules โ anything that compiles C/C++ code like sharp, bcrypt, sqlite3, canvas, or node-sass โ go through a two-stage build: compile, then link. This error means linking failed. The object files exist; clang just couldn't wire them into a final binary.
Root Causes
A macOS upgrade is usually the trigger. Several things can break at once:
- Xcode Command Line Tools are missing, outdated, or silently corrupted after a macOS update
- The macOS SDK path shifted and clang can no longer find system libraries
- Architecture mismatch โ building x86_64 on Apple Silicon (M1/M2/M3) or vice versa
- System libraries or frameworks the native module links against are missing
- Node.js version and node-gyp version are incompatible
- Wrong Python version (node-gyp requires Python 3.x, not 2.x)
Fix 1: Reinstall Xcode Command Line Tools
Nine times out of ten this is all you need โ especially right after upgrading macOS.
# Remove the existing (possibly broken) installation
sudo rm -rf /Library/Developer/CommandLineTools
# Trigger fresh install
sudo xcode-select --install
A dialog will pop up to confirm. Once it finishes:
# Verify the path is set correctly
xcode-select -p
# Should output: /Library/Developer/CommandLineTools
# Check clang works
clang --version
Retry npm install. If it passes, you're done.
Fix 2: Accept Xcode License and Reset the SDK Path
Full Xcode (not just CLT) has a license agreement that can silently block builds when it's pending:
sudo xcodebuild -license accept
Then check where xcode-select is pointing:
# If you have full Xcode installed
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
# Or point back to CLT if you don't need full Xcode
sudo xcode-select -s /Library/Developer/CommandLineTools
Fix 3: Pin the SDK Path Manually
Clang sometimes knows where it lives but loses track of the SDK โ particularly after incremental macOS updates that shift SDK versions from, say, MacOSX13.sdk to MacOSX14.sdk. Pin it explicitly:
# Find your current SDK path
xcrun --show-sdk-path
# Example: /Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk
# Export before building
export SDKROOT=$(xcrun --show-sdk-path)
npm install
Make it permanent so the next terminal session doesn't break again:
echo 'export SDKROOT=$(xcrun --show-sdk-path)' >> ~/.zshrc
source ~/.zshrc
Fix 4: Architecture Mismatch on Apple Silicon
M1/M2/M3 Macs run arm64. If your Node.js was installed via Rosetta or an x86_64 shell, the linker tries to target the wrong architecture and fails.
# Check your Node.js architecture
node -e "console.log(process.arch)"
# Should print 'arm64' on Apple Silicon, not 'x64'
# Check if you're running under Rosetta
rosetta --version 2>/dev/null || echo "Not using Rosetta"
Getting x64 on an arm64 Mac? Reinstall Node.js natively. With Homebrew:
# Confirm Homebrew itself is arm64
file $(which brew)
# Should say: Mach-O 64-bit executable arm64
brew uninstall node
brew install node
With nvm:
nvm install 20 --default
nvm use 20
node -e "console.log(process.arch)" # arm64
Fix 5: Clear npm Cache and Rebuild Clean
Broken build artifacts get cached. Even after fixing the root cause, npm may keep replaying the same broken build from cache.
npm cache clean --force
rm -rf node_modules
npm install
Targeting one specific package instead of reinstalling everything:
npm rebuild sharp
# or
npm rebuild bcrypt
Fix 6: Update node-gyp
Old versions of node-gyp don't always recognize newer Node.js releases or updated SDK paths. Node.js 22, for example, requires node-gyp 10+.
npm install -g node-gyp@latest
# Rebuild with the updated version
node-gyp rebuild
Some packages bundle their own older node-gyp. Override it like this:
npm_config_node_gyp=$(npm root -g)/node-gyp/bin/node-gyp.js npm install
Fix 7: Find the Missing Library
Verbose output tells you exactly what the linker couldn't find โ much more useful than the generic exit code:
npm install --verbose 2>&1 | grep -A5 'linker command failed'
Or run clang with the -v flag the error itself suggests. Common culprits on fresh macOS installs:
- OpenSSL (missing on macOS 12+):
brew install openssl, thenexport OPENSSL_ROOT_DIR=$(brew --prefix openssl) - libvips (sharp):
brew install vips - libpng / libjpeg:
brew install libpng libjpeg
After installing, point pkg-config at the new library and rebuild:
export PKG_CONFIG_PATH="$(brew --prefix openssl)/lib/pkgconfig:$PKG_CONFIG_PATH"
npm rebuild
Verification
One quick sanity check before calling it done:
# Tail the last 20 lines โ errors jump out immediately
npm install 2>&1 | tail -20
# Try loading the module
node -e "require('your-module-name'); console.log('OK')"
# Concrete example with bcrypt
node -e "const b = require('bcrypt'); console.log(b.getRounds(b.hashSync('test', 10)))"
No exception thrown means linking succeeded and the module is live.
Prevention
- Run
sudo xcode-select --installproactively after every macOS upgrade โ don't wait for a build to fail at 2 AM - Keep
SDKROOTin your shell profile so SDK path changes don't silently break future installs - Lock down Node.js architecture across your team โ document the expected
process.archin your README - Pin node-gyp to a recent version in devDependencies if you maintain a native module yourself
- On GitHub Actions, explicitly install Xcode CLI tools or use a macOS runner image that pre-installs them

