Repository navigation
macOS: gyp omits -isysroot when binding.gyp doesn't set SDKROOT, causing clang to resolve SDK via a path that ignores DEVELOPER_DIR/xcode-select #3344
Description
Environment
- node-gyp version: 9.4.1
- Node.js version: v20.18.1
- OS: macOS 26 (Tahoe) 26.5.1
- Xcode: 16.2.0 (Apple clang 16.0.0),
xcode-select -p->/Applications/Xcode-16.2.0.app/Contents/Developer - Command Line Tools: 26.6 (installed via Software Update, independent of Xcode.app)
Bug description
On macOS, node-gyp rebuild invokes clang++ without either -isysroot or --sdk. This makes the clang driver fall back to its own internal call to xcrun --show-sdk-path (with no --sdk argument). Verified via xcrun -v that this specific call variant resolves via a cache key hard-coded to /Library/Developer/CommandLineTools/SDKs/MacOSX.sdk -- independent of xcode-select/DEVELOPER_DIR, and independent of whichever Xcode toolchain actually supplies the invoked clang++ binary. (By contrast, xcrun --sdk macosx --show-sdk-path -- i.e. with an explicit SDK name -- does respect DEVELOPER_DIR/xcode-select.)
On systems where the OS has been updated (bumping the standalone CommandLineTools SDK) but the selected Xcode.app has not, this pairs a newer SDK's libc++ headers (which reference builtins like __builtin_ctzg/__builtin_clzg, requiring Clang >= 19) with an older embedded clang (16.0.0 from Xcode 16.2), producing:
error: use of undeclared identifier '__builtin_ctzg'
Root cause in gyp's own code
gyp/pylib/gyp/xcode_emulation.py already contains logic that avoids this, by explicitly naming the SDK when querying xcrun, and by passing -isysroot:
# GetSDKVersionInfoItem
return GetStdoutQuiet(["xcrun", "--sdk", sdk, infoitem]) # explicit --sdk, respects DEVELOPER_DIR
# GetCflags
if "SDKROOT" in self._Settings() and sdk_root:
cflags.append("-isysroot %s" % sdk_root) # only appended if SDKROOT is explicitly set in xcode_settings(Line numbers 525 / 581-582 in the 9.4.1 tag I actually hit this on; confirmed the exact same structure -- same gating -- is still present at line 522 / 578-579 in the current latest release, v13.0.1, published 2026-07-02. Also checked that release's CHANGELOG for any macOS SDK/clang-related fix; found none.)
This mirrors what CMake does (Modules/Platform/Darwin-Initialize.cmake: xcrun --sdk macosx --show-sdk-path), which correctly resolves the SDK paired with the active developer directory, and does not hit this issue on the same machine/OS update.
However, -isysroot is only emitted if the target's binding.gyp (or a shared .gypi) sets SDKROOT under xcode_settings. Neither node-gyp's own addon.gypi, node's cached common.gypi, nor common third-party native modules (checked diskusage, better-sqlite3, node-pty) set SDKROOT. As a result, no -isysroot is passed for ordinary builds, and clang falls through to the SDK-resolution path described above.
Steps to reproduce
- On a Mac where the standalone CommandLineTools SDK has been updated (via Software Update) to a version requiring Clang >= 19 builtins, while
xcode-selectstill points at an older Xcode with an older embedded clang. npm install/yarn installany package with a native addon (e.g.diskusage,better-sqlite3,node-pty).node-gyp rebuildfails with__builtin_ctzg/__builtin_clzgundeclared identifier errors, even thoughxcodebuild/CMake-based builds on the same machine succeed.
Expected behavior
The SDK-resolution path gyp uses by default (when SDKROOT is not explicitly configured) should pair the SDK with the actual compiler in use -- e.g. by always querying xcrun --sdk macosx --show-sdk-path instead of the no---sdk variant -- rather than relying on a system-wide default that can silently drift out of sync with the selected Xcode/CLT compiler.
Workaround (confirmed working)
Pin toolchain and SDK together, either direction:
DEVELOPER_DIR=/Library/Developer/CommandLineTools npm install # newer clang + newer SDK, pairedSDKROOT=/Applications/Xcode-16.2.0.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX15.2.sdk npm install # older clang + older SDK, pairedRelated
- Same root cause, labeled
bugand fixed upstream: terasakisatoshi/CxxFork.jl#24 -- "Avoid blindly preferringMacOSX.sdk/ latest SDK symlinks when their libc++ headers are incompatible with the embedded Clang path." - Broader context on the OS/Xcode SDK version mismatch during the macOS 26 transition: Homebrew/brew#21216, winfunc/opcode#233
- Checked node-gyp's own issue tracker first (open+closed+PRs) for
builtin_ctzg,isysroot,SDKROOT,DEVELOPER_DIR,CommandLineTools,Tahoe/"macOS 26" -- closest existing issues (node-gyp fails on Sonoma with CLI only and not XCode (Mac M1 / Apple Silicon) #2992, node-gyp build fails obscurely if no Xcode or CLT are found #3064, Xcode detection issue #3137) are about detecting the Xcode/CLT version, not this SDK/compiler pairing mismatch, so this appears to be a distinct report.
Activity
cclauss commentedon Jul 9, 2026
Please create a pull request:
Commit 1: A GitHub Action that fails because of this problem.
Commit 2: A fix that also enables the GitHub Action to pass.
cclauss commentedon Jul 9, 2026
Node-gyp v13 is current.
Node.js v20 is no longer supported.
https://nodejs.org/en/about/previous-releases#looking-for-the-latest-release-of-a-version-branch
Can you please replicate on current software.
Steps to reproduce 1 seems like a misconfiguration that even Apple would not support.
solarispika commentedon Jul 13, 2026
Reproduced on Node.js v24.15.0 + node-gyp v13.0.1, minimal repro (
std::sorton astd::vector<int>, noSDKROOTinbinding.gyp):So this isn't limited to the originally reported Node/node-gyp versions. The relevant
SDKROOTgating inxcode_emulation.pyis unchanged between v9.4.1 and v13.0.1, consistent with the same mechanism still applying.On the host configuration: the standalone CommandLineTools SDK can be updated by Software Update independently of whichever Xcode.app is selected via
xcode-select/DEVELOPER_DIR. The same class of failure (newer CLT SDK headers vs. an older paired compiler) has shown up in other projects too, e.g. winfunc/opcode#233 (worked around withSDKROOTpointing at the older SDK) and terasakisatoshi/CxxFork.jl#24 (fixed by no longer blindly preferring the newer SDK symlink when its libc++ headers are incompatible with the embedded Clang).Also, on this machine Xcode 16.2's GUI no longer launches on macOS 26 (
spctl -a -vv: "a sealed resource is missing or invalid"). Regardless of that, the command-line toolchain in that same Xcode still builds successfully when tools explicitly resolve the SDK throughxcrun --sdk macosxunderDEVELOPER_DIR-- our CI does exactly that via CMake, on this same macOS 26 machine, no errors. The observable difference is that node-gyp doesn't pass an explicit SDK path in this case unlessSDKROOTis set inbinding.gyp.Attached a minimal repro repo (
binding.gyp+ one.ccfile), including the setup recipe I used (viaxcodes+softwareupdate) for anyone who isn't already in this state.node-gyp-sdk-repro.zip