Fix dyld "missing symbol called" in Node on an M1 Mac
Find which of Node, the compiled addon and the system library is on the wrong CPU architecture, line them up, and know what the follow-up errors mean.
TL;DR#
dyld[<pid>]: missing symbol called on an Apple Silicon Mac means a native Node.js addon called a C function that dyld could not find when it loaded the addon. In the common case the addon was compiled for one CPU architecture (often x86_64, because Node.js itself was an Intel build running under Rosetta) and linked against a native library built for the other (arm64). The macOS linker drops a library of the wrong architecture with only a warning, and node-gyp links addons with -undefined dynamic_lookup, so the build "succeeds" and the crash waits for the first call. Fix it by putting Node.js and the native library on one architecture, then deleting node_modules and reinstalling so the addon is recompiled.
The error#
The Stack Overflow question comes from a developer running the backend of animo/simple-demo with Node.js v16.8.0 on an M1 Mac. The process dies with one line:
dyld[17378]: missing symbol calledThe asker had reinstalled Node.js five times in one day and still got x64 from node -p "process.arch". That detail is the whole bug. The demo's server depends on @aries-framework/node, whose published manifest pulls in indy-sdk. indy-sdk is a native addon: its install script is node-gyp rebuild, and its binding.gyp links the compiled indynodejs.node against the system library with -lindy.
A few weeks earlier the same symptom, described in the same words, was reported on the framework's own tracker as credo-ts issue #1039, where yarn alice ended with:
dyld[42534]: missing symbol called
error Command failed with signal "SIGABRT".A project contributor asked for two things: where libindy was and what file said about it and about node. The answer in that thread: libindy.dylib was an arm64 library, while the Node.js binary under ~/.nvm was x86_64. The contributor's diagnosis was exactly that mismatch, and the fix was an arm64 Node.js plus a fresh node_modules.
Why it happens#
Four pieces of Apple and Node.js tooling combine, and each is visible in source code.
1. node-gyp tells the linker to leave undefined symbols for later. On macOS, every addon built by node-gyp gets this in addon.gypi:
'xcode_settings': {
'OTHER_LDFLAGS': [
'-undefined dynamic_lookup'
],
},With that flag, a symbol the linker cannot find in any linked library does not fail the build: it is left for dyld to look up at run time (step 3 shows how).
2. The linker skips a library of the wrong architecture with a warning. In Apple's open-source linker (InputFiles.cpp in ld64, the published source linked here), an input file built for another CPU raises building for %s-%s but attempting to link with file built for %s-%s, which the linker turns into ignoring file %s, %s and carries on. So when an x86_64 build of indynodejs.node meets an arm64-only libindy.dylib, the library is simply left out, and every indy_* function the addon calls becomes an undefined symbol.
3. Those undefined symbols are marked "look them up anywhere at run time". In the same ld64 source, OutputFile.cpp gives a symbol with no defining library the special ordinal BIND_SPECIAL_DYLIB_FLAT_LOOKUP when -undefined dynamic_lookup is in effect. Nothing records that the symbol was supposed to come from libindy.
4. dyld cannot find them, and parks lazy calls on an abort stub. When Node.js loads the addon, dyld's Loader::resolveSymbol (Loader.cpp, tag dyld-1165.3) searches every loaded image for a flat-lookup symbol. If none has it, a weak import is bound to NULL; a lazily bound reference is bound to __dyld_missing_symbol_abort, with the comment "lazy bound symbols can be bound to __dyld_missing_symbol_abort"; anything else fails with symbol not found in flat namespace '%s'. The abort stub itself, in DyldAPIs.cpp at the same tag, is a single call: halt("missing symbol called"). The addon loads fine, your JavaScript starts, and the process aborts the moment it first calls into the missing library, which is why yarn reports SIGABRT rather than a JavaScript stack trace.
Rosetta is how the x86_64 side usually appears. The nvm README explains that macOS runs an Intel-only executable through Rosetta automatically, and that a shell started with arch -x86_64 zsh (or a terminal app ticked "Open using Rosetta") is an Intel shell, and its own walkthrough ends with node -p process.arch printing x64. nvm picks the build to install from uname -m (see nvm_get_arch in nvm.sh), so the shell you install from decides which Node.js you get. node-gyp in turn compiles for process.arch unless told otherwise: create-config-gypi.js sets target_arch from an explicit architecture option when one is given, falling back to process.arch. An Intel Node.js therefore produces Intel addons. Homebrew keeps both worlds apart on purpose: its FAQ says the /opt/homebrew prefix was chosen so that an Apple Silicon install there and a Rosetta 2 install in /usr/local can coexist. The prefix does not prove the architecture, though: in credo-ts #1039 the arm64 libindy sat in /usr/local/lib. Always ask file.
Fix#
The goal is one architecture for three things: the node binary, the compiled .node addon, and every native library the addon links.
- Find out what you have. These are the checks the contributor asked for in credo-ts #1039:
which node
file "$(which node)"
node -p process.arch- Check the native library the addon links. Use the library your package needs (
libindyhere);lipo -archsprints the architectures inside a Mach-O file, as used in node-foundationdb #50:
file /usr/local/lib/libindy.dylib
lipo -archs /usr/local/lib/libindy.dylib- If
nodeand the library disagree, pick arm64 for both whenever every library the addon needs exists as an arm64 build (step 2 tells you) and the Node.js release allows it. Node.js publishes macOS arm64 builds from v16.0.0 onwards, according to the official release index and the nvm README, so any supported release has one. Remove the Intel Node.js (and any second copy from a.pkgor Homebrew, which is what the reporter of credo-ts #1039 eventually found), open a terminal that is not running under Rosetta, and reinstall:
arch
nvm deactivate
nvm uninstall 22
nvm install 22
node -p process.archnvm deactivate comes first because nvm refuses to remove the version in use ("Cannot uninstall currently-active node version", in nvm.sh). arch must print arm64 before you install. If it does not, close the terminal and untick "Open using Rosetta" for the terminal app.
- Rebuild the addon for the new Node.js. A
.nodefile compiled for the old binary stays innode_modulesuntil you remove it:
rm -rf node_modules
npm install(With Yarn 1, run yarn cache clean and then yarn install, as the second Stack Overflow answer does.)
- If arm64 is not possible, go the other way and make everything Intel. Two cases force it. One is a library that only exists as
x86_64: in node-foundationdb #50 the owner'slipo -archsonlibfdb_c.dylibprinted onlyx86_64, and a user got it working by installing Node.js and npm under Rosetta. The other is a Node.js release older than v16.0, which the nvm README documents compiling forx86_64insidearch -x86_64 zsh. The gist linked from the top-voted answer takes this route by reinstalling Node.js inside a Rosetta shell. It only works if every native library the addon links is also anx86_64build, for example from a Homebrew installed under Rosetta in/usr/local.
Verify the fix#
Check the addon itself, not just Node.js. For indy-sdk the compiled file is the one named in the follow-up error of credo-ts #1039:
file node_modules/indy-sdk/build/Release/indynodejs.node
node -p process.archfile should report the same architecture as node -p process.arch and as the library. Then start the app and exercise the code path that crashed: because the abort is triggered on the first call into the missing library, a server that merely boots proves nothing.
To see the linker's own output, rebuild the package with its install script in the foreground. The foreground-scripts setting is off by default; turned on, it runs build scripts in the foreground, sharing standard output and error with npm, and npm rebuild accepts it:
npm rebuild indy-sdk --foreground-scriptsAn ignoring file ... building for ... but attempting to link with file built for ... warning in that output is the moment the crash was created.
One email a month — the latest explained errors
What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.
When the architectures already match#
The same message turns up in other ways, and the fix differs:
- The addon's own source skips a file on arm64. In discordjs/opus #165 (Apple M2, Node.js v22.11.0, as stated in the issue), encoding crashed with
missing symbol called. The reporter traced it tobinding.gyp: an arm64-specific Opus source file was not compiled under thetarget_arch=="arm64"condition, so a function it defines was never built. The fix belongs in the package, so update it, patch it, or switch to an alternative; reinstalling Node.js will not help. node-fast-crc32c #38 is another report of the same message on M1, where commenters worked around it by running x64 Node.js or with a source patch. - Node.js from Nix. In node-rdkafka #1084 (Node.js 20.12.2 on an M1, as stated in the issue) a plain
require("node-rdkafka")crashed with the same message, using a Node.js from Nix. The fix posted in the thread was to switch to a Node.js installed by nvm; the poster suspected that the Nix build does not compile native modules with Xcode's toolchain, which is a guess, not a confirmed cause. - The sibling message. If dyld reports
symbol not found in flat namespace '<name>'while the addon is being loaded, it is the same lookup failing for a reference that was not lazily bound, per theresolveSymbolcode above. The diagnosis (which library did not get linked, and why) is the same.
For prebuilt binaries that are missing for your platform rather than mislinked, the failure looks different: see grpc-tools failing with a 404 on darwin-arm64.tar.gz and Next.js unable to load its SWC binary. If npm install stops before compiling anything with gyp: No Xcode or CLT version detected!, the Command Line Tools are the problem: fix node-gyp's Xcode check first.
The error that comes next#
Switching to a different Node.js major without rebuilding produces a different, more helpful failure. In credo-ts #1039 the reporter's reinstall also changed the Node.js major, and loading indynodejs.node then threw was compiled against a different Node.js version using NODE_MODULE_VERSION, with code: 'ERR_DLOPEN_FAILED': the addon had been built by another major than the one loading it. The contributor's answer was again to delete node_modules and reinstall. Step 4 above prevents it, whichever major you land on. It is the same family as node-sass failing to find a binding for the current environment: a compiled module built by one Node.js and loaded by another. The same discipline applies in containers, where an image built on an Apple Silicon Mac is arm64 and an x86 host refuses it with exec format error.
The short version for an Apple Silicon Mac is that missing symbol called is never fixed by reinstalling Node.js again and again. It is fixed when file reports one architecture for node, for the .node addon, and for each library the addon links, and the addon has been recompiled after the last change to any of them. The sources above (node-gyp's addon.gypi, ld64 and dyld) are read directly; this page did not reproduce the crash, because it only exists on macOS.
Frequently Asked Questions
Still stuck? Paste your error.
Paste the error message or stack trace — we check it against every fix and tool on the site.
Continue Reading
npm EACCES without sudo: fix the cache and global prefix
Reproduced on npm 11.19.0: how to tell the cache EACCES from the global-install EACCES by the path line, and the two fixes npm documents that end sudo for good.
Fix: Local package.json exists, but node_modules missing
Why cloning a repository never brings its dependencies with it, and the one command that turns a missing-binary failure into a working start script.
Fix 'Missing parameter name at 1' in Express 5 routes
Your Express 5 server crashes at start-up. One changed route string brings it back, and on current installs the message itself names the route to change.


