Repository navigation
Description
Summary
opencode plugin add/plugins fails with NpmInstallFailedError: git dep preparation failed for any git dependency whose package.json has a
build, prepare, preinstall, install, postinstall, or prepack
script, or a workspaces field — regardless of git host. Root cause: the
compiled opencode binary re-invokes itself as the "npm" binary for pacote's
git-dependency prepare step, but its own CLI doesn't know how to run that,
so it just prints help and exits instead of running the prepare install.
Environment
- opencode version: 2.0.7 (channel: latest)
- OS: Darwin 25.6.0 (macOS, darwin arm64)
- Shell: /bin/zsh
- Install: global npm-style install (
@opencode/cli), compiled binary
Reproduction
Fully public, no auth/network involved beyond a normal git clone:
opencode plugin add "git+ssh://[email protected]/lnwu/opencode-plugin-repro-withbuild.git"That repo's package.json is just:
{
"name": "opencode-plugin-repro-withbuild",
"version": "1.0.0",
"scripts": { "build": "echo building" }
}Renaming that one script key to anything other than build / prepare /
preinstall / install / postinstall / prepack (and not using
workspaces) makes the exact same install succeed — nothing else about the
repo changes. This is host-independent: I originally suspected a self-hosted
git server was the trigger, but a scriptless package installs fine from that
same self-hosted host, and this scripted package fails from github.com too.
Only the presence of one of those script names (or workspaces) matters.
Root Cause
opencode plugin add installs git specifiers through npm's real
@npmcli/arborist (packages/core/src/npm.ts, Npm.reify →
arborist.reify()), not Bun's own installer. Arborist's git fetching goes
through pacote's GitFetcher. pacote/lib/git.js's #prepareDir runs an
extra step only when the cloned package.json declares one of those
scripts or workspaces — it shells out to a real npm install inside the
clone so any prepare-style script has its own deps available:
// pacote/lib/git.js
return npm(
this.npmBin,
[].concat(this.npmInstallCmd).concat(this.npmCliConfig),
dir,
{ ...process.env, _PACOTE_NO_PREPARE_: noPrepare.join('\n') },
{ message: 'git dep preparation failed' } // <- the error we see
)pacote's npmBin defaults to the string 'npm' (resolved via PATH), but
opencode's server log shows it's configured to a path inside its own bundled
filesystem instead:
args=["/$bunfs/bin/npm-cli.js","install","--force","--cache=...", ...]
pacote/lib/util/npm.js spawns this as <process.execPath> <npmBin> <args...> (the same way you'd run node npm-cli.js install ...). That's
correct for a normal Node runtime, but process.execPath for opencode's
compiled binary is the opencode binary itself — so this ends up invoking
opencode with /$bunfs/bin/npm-cli.js install --force ... as CLI arguments.
opencode's own CLI parser doesn't recognize those as a valid subcommand, so
it just prints help and exits (confirmed in the server log, immediately
before the NpmInstallFailedError):
message="cli starting" args=["/$bunfs/bin/npm-cli.js","install","--force", ...]
message="cli process failed" cause="Cause([Fail(~effect/cli/CliError/ShowHelp: Help requested)])"
message="cli process failed" cause="Cause([Fail(NpmInstallFailedError (cause: Error: git dep preparation failed))])"
So the nested npm install never actually runs, pacote's prepare step fails,
and opencode's error wrapper discards the real cause down to the generic
NpmInstallFailedError.
Confirmed by calling the exact same Arborist.reify() opencode calls
directly (outside opencode, both as a plain script and as a bun build --compile'd binary) — it installs the same git dependency successfully in
both cases, because npmBin isn't overridden to a bunfs path there.
Expected Behavior
Per the plugins docs ("Git
repositories can use hosted shortcuts, HTTPS, or SSH..."), any git dependency
should install the same way npm/bun would handle it, including ones with
a build/prepare script.
Workaround
None needed if you control the package: avoid build/prepare/
preinstall/install/postinstall/prepack script names (rename them) and
avoid workspaces in the installed package's package.json, and ship any
build output pre-built/committed instead. Not viable for git dependencies you
don't control.
Activity
chore: drop lifecycle/build scripts so github-spec plugin installs work
vvo commentedon Oct 7, 2026
Still happens on 2.0.24 (macOS arm64). We ran into it with
opencode plugin add github:vercel/vercel-plugin: that repo has abuildscript, so the install dies withgit dep preparation failed. With the script renamed, the same package installs in about 3 seconds, which is the workaround we shipped in vercel/vercel-plugin#314.Npm.reifyalready passesignoreScripts: true, so the prepare step can't do anything useful for these installs anyway. Skipping it for git deps would fix this without touching how npm gets spawned.build: rename build script out of pacote's git-prepare trigger set
fix: rename build script so opencode can git-install the plugin
fix: rename build script so OpenCode 2 git installs work
fix: rename build script so OpenCode 2 git installs work
UE-DND commentedon Oct 21, 2026
Same issue still confirmed on 2.1.2 (macOS arm64).
BoysGameStudio commentedon Oct 23, 2026
Same issue still confirmed on 2.1.3 (macOS arm64).
one block message for both hosts, a generic fallback, and install-oc.sh
opencodejr commentedon Oct 28, 2026
Thanks for the detailed report. I can confirm this.
I set up two local git repos with the same minimal plugin. One has
"scripts": {"build": "echo building"}and the other uses the same script under a different key. I ranopencode plugin add git+file://<repo>for each:buildinstalls. The one withbuildfails withNpmInstallFailedErrorcaused bygit dep preparation failed. The log shows the binary starting itself again with/$bunfs/bin/npm-cli.js install --force ...and exiting with ShowHelp, as you describe.dev(950111ff2) run from source: the install fails with the samegit dep preparation failed.The npm config opencode builds appears to point npm's binary at an
npm-cli.jsunder its own package. That file doesn't exist when running from source. In the compiled binary, the path ends up being run byopencodeitself, so pacote's prepare step can never run.The script below reproduces it without network access.
Reproduction script (repro.sh)
Run it:
bash repro.shybbuc commentedon Nov 6, 2026
Reproduced on 2.2.5 (macOS arm64), which is newer than the versions above.
opencode plugin add 'github:ybbuc/opencode-plugins#main::path:peak'fails withNpmInstallFailedError: git dep preparation failed. The repo is public.peakandquotaboth have"build": "vite build"in theirpackage.json.In a local copy, renaming that key to
build:distand installing withgit+file://…#mainexits 0 on 2.2.5. Thegithub:andgit+https://forms fail the same way without the rename.The debug log shows the same
args=["/$bunfs/bin/npm-cli.js","install","--force",…]followed byShowHelpthat you describe.build(opencode): rename build script to build:dist
JoshRob297 commentedon Nov 9, 2026
Independent reproduction, plus the detail that decides the fix.
Environment: Debian 12 container that never had opencode, installed with the official installer (1.19.5). Also reproduced on 2.2.6.
What the child process actually is
strace -f -e trace=execveoveropencode plugin github:...:gitexecs, zeronpmexecsopencode /$bunfs/bin/npm-cli.js --versionprints1.19.5— it runs its own CLI, ignores the script, exits 0. No npm debug log is ever written, which fits npm never running.Not specific to one repo:
github:numman-ali/opencode-openai-codex-auth(about 2.2k stars) fails identically, as does the repo in this report.Isolated from opencode, with the
[email protected]this repo pins:pacote.tarball(spec, { npmBin: "/$bunfs/bin/npm-cli.js" })→git dep preparation failedpacote.tarball(spec, { npmBin: "npm" })→ preparation passesSo the trigger is
npmBin, not the host nor the script itself.The consequence that decides the fix
With
@npmcli/arborist, same options asNpm.reify, on a Git dependency whosepreparebuilds the entry point:ignoreScripts: true(anynpmBin) → install succeeds,node_modules/<pkg>/distabsentignoreScripts: false,npmBin: "npm"→ install succeeds,distpresentHonouring
ignoreScriptstherefore removes the error but ships the plugin unbuilt:main: ./dist/index.jsdoes not exist and OpenCode loads nothing — a silent failure replacing a loud one, for every plugin that compiles during install.Opened #62817 to hand pacote a real npm, falling back to the current behaviour when none is on
PATH.