Fix grpc-tools 404 on darwin-arm64.tar.gz (Mac M1)
Get grpc_tools_node_protoc running on an Apple Silicon Mac, understand why every workaround still needs Rosetta 2, and check which binary actually landed in node_modules.
TL;DR#
grpc-tools does not compile anything at install time: its install script is plain node-pre-gyp install, which downloads a prebuilt protoc and grpc_node_plugin named after your platform and CPU. Up to version 1.11.2 nobody uploaded a darwin-arm64.tar.gz, so an arm64 Node.js on an M1 Mac asks for a file that returns 404, and the install fails. Since 1.11.3 that file exists, so upgrading grpc-tools ends the 404. What it contains is still an Intel (x86_64) build, so Rosetta 2 must be installed for grpc_tools_node_protoc to run. Every popular workaround (npm_config_target_arch=x64, an x64 Node.js under Rosetta) lands on Intel binaries.
The error#
The Stack Overflow question (12 votes, no accepted answer) ran npm install -g grpc-tools with Node.js 16.13.1 on an M1 Mac running Big Sur and got:
npm ERR! code 1
npm ERR! path /Users/abdulmoiz_ahmer/.nvm/versions/node/v16.13.1/lib/node_modules/grpc-tools
npm ERR! command failed
npm ERR! command sh -c node-pre-gyp install
npm ERR! response status 404 Not Found on https://node-precompiled-binaries.grpc.io/grpc-tools/v1.11.2/darwin-arm64.tar.gz
npm ERR! node-pre-gyp info using node-pre-gyp@1.0.8
npm ERR! node-pre-gyp info using node@16.13.1 | darwin | arm64
npm ERR! node-pre-gyp http GET https://node-precompiled-binaries.grpc.io/grpc-tools/v1.11.2/darwin-arm64.tar.gz
npm ERR! node-pre-gyp ERR! install response status 404 Not Found on https://node-precompiled-binaries.grpc.io/grpc-tools/v1.11.2/darwin-arm64.tar.gz
npm ERR! node-pre-gyp ERR! System Darwin 20.4.0
npm ERR! node-pre-gyp ERR! not okTwo lines carry the diagnosis. using node@16.13.1 | darwin | arm64 is the platform and architecture node-pre-gyp detected, and the URL that follows is built from exactly those two words. The same output, word for word on the 404 lines, was posted to the grpc-node tracker in issue #2036 with Node.js 17.4.0, so it does not depend on the Node.js version.
Why the download is a 404#
The grpc-tools package.json on the npm registry declares a binary block with the host https://node-precompiled-binaries.grpc.io/ and the file name {platform}-{arch}.tar.gz. node-pre-gyp fills those placeholders from Node's own process.platform and process.arch unless you override them (node-pre-gyp README). An arm64 Node.js on macOS therefore requests darwin-arm64.tar.gz.
The install script has no fallback to a source build, so a missing file is fatal. The HTTP status of that file, version by version, checked on 20 October 2026:
| grpc-tools | darwin-arm64.tar.gz | darwin-x64.tar.gz |
|---|---|---|
| 1.11.2 (June 2021) | 404 (NoSuchKey) | 200 |
| 1.11.3 (October 2022) | 200 | 200 |
| 1.12.0 to 1.12.4 | 200 | 200 |
| 1.13.0, 1.13.1 (latest) | 200 | 200 |
The build script explains the gap. At the grpc-tools@1.11.2 tag, build_binaries.sh only builds arch_list=( x64 ) on macOS. In October 2022, PR #2235 added the arm64 artefact, and the maintainer confirmed in that thread: "This has been published in grpc-tools version 1.11.3." Before that, a maintainer had written in issue #2036 that "grpc-tools has never worked on ARM".
The arm64 tarball holds Intel binaries#
The darwin-arm64 file that 1.11.3 introduced does not contain arm64 code. The same script, at both the 1.11.3 tag and the 1.13.1 tag, builds a universal binary and then extracts the Intel slice into both archive folders. At 1.11.3:
63: for arch in "x86_64" "arm64"; do
66: lipo -extract x86_64 $base/build/bin/$bin -o $base/build/bin/$arch/$binAt 1.13.1, the loop is renamed but still extracts x86_64 for both:
76: for arch in "x64" "arm64"; do
79: lipo -extract x86_64 "$base/build/bin/$bin" -o "$base/build/bin/$arch/$bin"Reading the Mach-O header of the published files confirms it. This is the file output for the protoc inside the 1.13.1 darwin-arm64.tar.gz, downloaded on 20 October 2026:
b/arm64/protoc: Mach-O universal binary with 1 architecture: [x86_64:\012- Mach-O 64-bit x86_64 executable, flags:<NOUNDEFS|DYLDLINK|TWOLEVEL|WEAK_DEFINES|BINDS_TO_WEAK|PIE|HAS_TLV_DESCRIPTORS>]The 1.11.3 file gives the same result. A user reported this in issue #2378 in March 2023, with identical MD5 sums for the "arm64" and x86_64 protoc. The fix, PR #3045 ("Fix grpc-tools for Apple Silicon"), replaces that loop with two explicit lipo calls per binary, one extracting x86_64 into the x64 folder and one extracting arm64 into the arm64 folder. It was merged on 1 April 2026, and the GitHub compare API reports that merge commit as 17 commits ahead of the grpc-tools@1.13.1 tag. On 20 October 2026, 1.13.1 was still the registry's latest, so no published grpc-tools contains the fix.
Without Rosetta 2, the Intel binary cannot start. Issue #2646 shows what that looks like on an M3 Pro: Error: spawn Unknown system error -86 from grpc_tools_node_protoc, and bad CPU type in executable when protoc is run directly. The reporter fixed it with softwareupdate --install-rosetta.
How the fix was checked#
Nothing here was run on Apple Silicon. node-pre-gyp accepts --target_platform and --target_arch to override the host values (node-pre-gyp README), and npm passes them through as npm_config_* environment variables. The download step was therefore reproduced on Linux, in the official node:20-alpine image (Node.js 20.20.2, npm 10.8.2), on 20 October 2026, with the darwin-arm64 target forced. Forcing the M1 target onto grpc-tools 1.11.2 gives the same 404:
npm_config_target_platform=darwin npm_config_target_arch=arm64 npm install grpc-tools@1.11.2npm error command sh -c node-pre-gyp install
npm error response status 404 Not Found on https://node-precompiled-binaries.grpc.io/grpc-tools/v1.11.2/darwin-arm64.tar.gz
npm error node-pre-gyp info using node@20.20.2 | linux | x64
npm error node-pre-gyp http GET https://node-precompiled-binaries.grpc.io/grpc-tools/v1.11.2/darwin-arm64.tar.gz
npm error node-pre-gyp ERR! install response status 404 Not Found on https://node-precompiled-binaries.grpc.io/grpc-tools/v1.11.2/darwin-arm64.tar.gz Changing only the version to 1.11.3 makes the install succeed (exit code: 0). The first 16 bytes of the protoc it unpacked into node_modules/grpc-tools/bin are a fat Mach-O header (cafebabe) with one architecture, CPU type 01000007, which is x86_64. An arm64 binary would carry 0100000c:
000000 ca fe ba be 00 00 00 01 01 00 00 07 00 00 00 03grpc-tools 1.13.1 under the same forced target gives the same bytes. With answer 1's npm_config_target_arch=x64 on 1.11.2, the install also succeeds, and its protoc is a thin x86_64 Mach-O:
000000 cf fa ed fe 07 00 00 01 03 00 00 00 02 00 00 00Every route ends on an Intel protoc. The upgrade is the one that needs no environment variable.
Fix#
-
Install Rosetta 2 once. It is the translation layer every route below relies on, and the command comes from answer 3 on the question:
bashsoftwareupdate --install-rosetta -
Upgrade grpc-tools so the download stops returning 404. In a project:
bashnpm install --save-dev grpc-tools@latestFor a global install, note that
yarn add global grpc-toolsfrom the question does not do what it looks like. The Yarn 1 documentation states: "Enteringyarn add global package-namewill add the packages namedglobalandpackage-namelocally instead of addingpackage-nameglobally." Use one of these:bashnpm install -g grpc-tools@latest yarn global add grpc-tools -
If you must stay on grpc-tools 1.11.2 or older, force the Intel download instead. This is the top-voted answer on the question, and it works because node-pre-gyp then asks for
darwin-x64.tar.gz, which returned 200 for every release checked, from 1.9.1 to 1.13.1:bashnpm_config_target_arch=x64 npm install grpc-tools@1.11.2When the old version comes in through another dependency, set the variable on the plain
npm installthat rebuilds the whole tree, since any environment variable starting withnpm_config_is read as npm configuration (npm config docs). Alternatively, lift the nested copy to a fixed release from your ownpackage.jsonwith npm's overrides field, and the variable is no longer needed. -
Regenerate your code as usual:
bashnpx grpc_tools_node_protoc --js_out=import_style=commonjs,binary:./generated --grpc_out=grpc_js:./generated -I ./protos ./protos/*.proto
The grpc_js option of --grpc_out makes the generated files require('@grpc/grpc-js') rather than the old grpc package (grpc-tools README).
Verify the fix#
Check which file landed and whether it starts, from the project root on the Mac:
node -p "process.platform + '-' + process.arch"
file node_modules/grpc-tools/bin/protoc
npx grpc_tools_node_protoc --versionAn arm64 Node.js prints darwin-arm64, and with grpc-tools 1.11.3 to 1.13.1 file reports an x86_64 binary, as shown above. That is expected, not a failed install. The last command spawns protoc itself, so it is the quickest Rosetta test: in the Linux run with grpc-tools 1.13.1 it printed libprotoc 3.19.1, and the step 4 command wrote helloworld_grpc_pb.js and helloworld_pb.js from a test helloworld.proto. If it fails with bad CPU type in executable or spawn Unknown system error -86, Rosetta 2 is missing, so go back to step 1.
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.
Installing with --ignore-scripts or under a script allowlist#
The second answer on the question installs with --ignore-scripts and then runs node-pre-gyp install --target_arch=x64 inside node_modules/grpc-tools by hand. That works for the same reason as step 3, but it shows a trap: grpc-tools ships no protoc in the tarball on npm. The executable only exists if its install script ran. With --ignore-scripts, or with any package manager that skips dependency scripts, grpc_tools_node_protoc has nothing to spawn. Installing grpc-tools 1.13.1 with that flag in node:20-alpine left only google, protoc.js and protoc_plugin.js in bin, and the generator then failed:
Error: spawn /w/node_modules/grpc-tools/bin/protoc ENOENTIf you see that line after an install that looked clean, the binary download never ran.
npm is moving that way too. The accepted npm RFC "Make install scripts opt-in" plans that dependency install scripts not listed in a new allowScripts field keep running with a warning first, and are skipped by default in a later major release. On 20 October 2026 in the official node:26-alpine image (Node.js 26.10.0, npm 11.19.1), a plain npm install grpc-tools still ran the script and put protoc in place, and printed:
npm warn install-scripts 1 package has install scripts not yet covered by allowScripts:
npm warn install-scripts grpc-tools@1.13.1 (install: node-pre-gyp install)
npm warn install-scripts
npm warn install-scripts Run `npm install-scripts ls` to review, or `npm install-scripts approve <pkg>` to allow.Approving grpc-tools now keeps the binary arriving when the default flips. With npm 11.19.1, the command from the warning added an allowScripts entry pinned to the installed version, and the next clean install ran without the warning:
npm install-scripts approve grpc-tools{
"allowScripts": {
"grpc-tools@1.13.1": true
}
}The RFC explains that a version-pinned entry stops matching once the package version changes, so approve again after each grpc-tools upgrade. The maintainers are discussing in issue #3061 how to ship the binaries without an install script.
Neighbouring native-binary errors#
The same family of failures shows up elsewhere in the Node.js toolchain. If you build a native module from source on a Mac instead and hit gyp: No Xcode or CLT version detected!, the Command Line Tools are the problem, and the node-gyp Xcode fix covers it. A node_modules folder copied from another machine fails in a similar way. Node Sass "could not find a binding" and the missing @rollup/rollup-win32-x64-msvc module both come from a node_modules installed under another OS or Node.js, and both are fixed by rebuilding or reinstalling on the machine that runs the code. If npm install -g grpc-tools gets past the download but stops on EACCES, the global prefix is owned by root: see npm EACCES without sudo rather than reaching for sudo.
On an Apple Silicon Mac, the practical state of grpc-tools is simple: any version from 1.11.3 installs, every published version up to 1.13.1 runs through Rosetta 2, and native arm64 binaries are waiting on a release that contains PR #3045. Once one ships, file node_modules/grpc-tools/bin/protoc reporting arm64 is how you will know you can drop Rosetta for this tool.
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
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.
Fix tar EPERM: operation not permitted, futime
Reproduce the ownership mismatch behind the warning, see how a current npm install actually fails, and fix the layer that creates it.
Fix Docker npm EBADENGINE: Unsupported engine
Read the Required and Actual lines, distinguish npm self-upgrades from application installs, and stop a floating npm release breaking an unchanged Dockerfile.


