Sửa lỗi 'clang: error: linker command failed with exit code 1' Khi Build Native Node.js Modules trên macOS

intermediate🍎 macOS2026-05-11| macOS 12–15, Node.js 16–22, npm/yarn/pnpm, native Node.js modules (node-gyp, node-pre-gyp)

Error Message

clang: error: linker command failed with exit code 1 (use -v to see invocation)
#node#npm#native-module#clang#build#macos

Lỗi gặp phải

Bạn chạy npm install hoặc npm rebuild và quá trình build thất bại với:

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

Các native module của Node.js — bất kỳ thứ gì biên dịch code C/C++ như sharp, bcrypt, sqlite3, canvas, hoặc node-sass — đều trải qua quá trình build hai giai đoạn: biên dịch, rồi liên kết (link). Lỗi này có nghĩa là giai đoạn liên kết thất bại. Các file object đã tồn tại; clang chỉ không thể kết nối chúng thành một file binary hoàn chỉnh.

Nguyên nhân gốc rễ

Nâng cấp macOS thường là nguyên nhân kích hoạt. Nhiều thứ có thể hỏng cùng lúc:

  • Xcode Command Line Tools bị thiếu, lỗi thời, hoặc bị hỏng âm thầm sau khi cập nhật macOS
  • Đường dẫn macOS SDK thay đổi và clang không còn tìm thấy các thư viện hệ thống
  • Kiến trúc không khớp — build x86_64 trên Apple Silicon (M1/M2/M3) hoặc ngược lại
  • Các thư viện hệ thống hoặc framework mà native module liên kết tới bị thiếu
  • Phiên bản Node.js và node-gyp không tương thích với nhau
  • Sai phiên bản Python (node-gyp yêu cầu Python 3.x, không phải 2.x)

Cách sửa 1: Cài lại Xcode Command Line Tools

Chín phần mười trường hợp đây là tất cả những gì bạn cần — đặc biệt ngay sau khi nâng cấp macOS.

# Xóa bản cài đặt hiện tại (có thể đã bị hỏng)
sudo rm -rf /Library/Developer/CommandLineTools

# Kích hoạt cài đặt mới
sudo xcode-select --install

Một hộp thoại sẽ hiện ra để xác nhận. Sau khi hoàn tất:

# Xác minh đường dẫn được thiết lập đúng
xcode-select -p
# Kết quả mong đợi: /Library/Developer/CommandLineTools

# Kiểm tra clang hoạt động
clang --version

Thử lại npm install. Nếu thành công, bạn đã xong.

Cách sửa 2: Chấp nhận giấy phép Xcode và đặt lại đường dẫn SDK

Xcode đầy đủ (không chỉ CLT) có một thỏa thuận cấp phép có thể âm thầm chặn quá trình build khi đang ở trạng thái chờ xác nhận:

sudo xcodebuild -license accept

Sau đó kiểm tra xem xcode-select đang trỏ đến đâu:

# Nếu bạn đã cài đầy đủ Xcode
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

# Hoặc trỏ lại CLT nếu bạn không cần Xcode đầy đủ
sudo xcode-select -s /Library/Developer/CommandLineTools

Cách sửa 3: Gán thủ công đường dẫn SDK

Clang đôi khi biết vị trí của mình nhưng lại mất dấu SDK — đặc biệt sau các bản cập nhật macOS tăng dần làm thay đổi phiên bản SDK từ, chẳng hạn, MacOSX13.sdk sang MacOSX14.sdk. Hãy gán cụ thể:

# Tìm đường dẫn SDK hiện tại
xcrun --show-sdk-path
# Ví dụ: /Library/Developer/CommandLineTools/SDKs/MacOSX14.sdk

# Export trước khi build
export SDKROOT=$(xcrun --show-sdk-path)
npm install

Đặt cố định để phiên terminal tiếp theo không bị hỏng lại:

echo 'export SDKROOT=$(xcrun --show-sdk-path)' >> ~/.zshrc
source ~/.zshrc

Cách sửa 4: Kiến trúc không khớp trên Apple Silicon

Máy Mac M1/M2/M3 chạy arm64. Nếu Node.js của bạn được cài qua Rosetta hoặc một shell x86_64, linker sẽ cố target sai kiến trúc và thất bại.

# Kiểm tra kiến trúc Node.js của bạn
node -e "console.log(process.arch)"
# Nên in ra 'arm64' trên Apple Silicon, không phải 'x64'

# Kiểm tra xem bạn có đang chạy qua Rosetta không
rosetta --version 2>/dev/null || echo "Not using Rosetta"

Nhận được x64 trên máy Mac arm64? Hãy cài lại Node.js theo kiến trúc gốc. Với Homebrew:

# Xác nhận Homebrew chính nó là arm64
file $(which brew)
# Kết quả mong đợi: Mach-O 64-bit executable arm64

brew uninstall node
brew install node

Với nvm:

nvm install 20 --default
nvm use 20
node -e "console.log(process.arch)"  # arm64

Cách sửa 5: Xóa cache npm và build lại sạch

Các artifact build bị lỗi sẽ được cache lại. Ngay cả sau khi sửa nguyên nhân gốc rễ, npm vẫn có thể tiếp tục replay cùng một build bị lỗi từ cache.

npm cache clean --force
rm -rf node_modules
npm install

Nếu chỉ muốn nhắm vào một package cụ thể thay vì cài lại tất cả:

npm rebuild sharp
# hoặc
npm rebuild bcrypt

Cách sửa 6: Cập nhật node-gyp

Các phiên bản cũ của node-gyp không phải lúc nào cũng nhận ra các bản phát hành Node.js mới hơn hoặc các đường dẫn SDK đã được cập nhật. Node.js 22, chẳng hạn, yêu cầu node-gyp 10 trở lên.

npm install -g node-gyp@latest

# Build lại với phiên bản đã cập nhật
node-gyp rebuild

Một số package đi kèm phiên bản node-gyp cũ hơn của riêng chúng. Ghi đè như sau:

npm_config_node_gyp=$(npm root -g)/node-gyp/bin/node-gyp.js npm install

Cách sửa 7: Tìm thư viện bị thiếu

Đầu ra chi tiết sẽ cho bạn biết chính xác linker không tìm thấy gì — hữu ích hơn nhiều so với mã thoát chung chung:

npm install --verbose 2>&1 | grep -A5 'linker command failed'

Hoặc chạy clang với flag -v như chính thông báo lỗi gợi ý. Các thủ phạm thường gặp trên bản cài macOS mới:

  • OpenSSL (thiếu trên macOS 12 trở lên): brew install openssl, sau đó export OPENSSL_ROOT_DIR=$(brew --prefix openssl)
  • libvips (sharp): brew install vips
  • libpng / libjpeg: brew install libpng libjpeg

Sau khi cài đặt, trỏ pkg-config đến thư viện mới và build lại:

export PKG_CONFIG_PATH="$(brew --prefix openssl)/lib/pkgconfig:$PKG_CONFIG_PATH"
npm rebuild

Kiểm tra kết quả

Một kiểm tra nhanh trước khi kết luận đã xong:

# Xem 20 dòng cuối — lỗi sẽ hiện ra ngay
npm install 2>&1 | tail -20

# Thử load module
node -e "require('your-module-name'); console.log('OK')"

# Ví dụ cụ thể với bcrypt
node -e "const b = require('bcrypt'); console.log(b.getRounds(b.hashSync('test', 10)))"

Không có ngoại lệ nào được ném ra nghĩa là quá trình liên kết đã thành công và module đang hoạt động.

Phòng ngừa

  • Chạy sudo xcode-select --install chủ động sau mỗi lần nâng cấp macOS — đừng đợi đến khi build thất bại lúc 2 giờ sáng
  • Giữ SDKROOT trong shell profile để các thay đổi đường dẫn SDK không âm thầm phá vỡ các lần cài đặt trong tương lai
  • Thống nhất kiến trúc Node.js trong toàn team — ghi rõ process.arch mong đợi trong README của bạn
  • Ghim node-gyp vào phiên bản mới trong devDependencies nếu bạn tự phát triển một native module
  • Trên GitHub Actions, cài đặt rõ ràng Xcode CLI tools hoặc dùng image macOS runner có cài sẵn chúng

Related Error Notes