You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Installing both @earendil-works/pi-ai and @earendil-works/pi-coding-agent as direct deps puts two identical copies of pi-ai on disk — one hoisted, one nested under pi-coding-agent. Since the API provider registry is a module-level Map, the two copies are separate module instances with separate registries. A provider I register through the hoisted copy is invisible to the agent loop, which imports pi-ai through the nested copy. Stream dispatch then fails with:
No API provider registered for api: <my-api>
and the agent session ends in ~120ms with empty output.
Cause: the published pi-coding-agent tarball ships an npm-shrinkwrap.json whose nested pi-ai entry has no integrity field, so npm won't dedupe it against the hoisted copy even though the bytes are identical.
Steps to reproduce
package.json with both as direct deps, then:
npm install
find node_modules -type d -name pi-ai -path "*@earendil-works*"# node_modules/@earendil-works/pi-ai# node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai
diff -rq node_modules/@earendil-works/pi-ai \
node_modules/@earendil-works/pi-coding-agent/node_modules/@earendil-works/pi-ai
# no output — the two copies are byte-identical
npm 10.9.8, node 22.22.3.
Expected behavior
One deduped pi-ai, so registerApiProvider and the agent loop share one registry. Fix options: don't ship a published npm-shrinkwrap.json from a library, or give its entries integrity hashes so npm can dedupe. Happy to PR if useful.
This issue was auto-closed. All issues from new contributors are auto-closed by default.
Maintainers review auto-closed issues daily and reopen worthwhile ones. Issues that do not meet the quality bar in CONTRIBUTING.md will not be reopened or receive a reply.
If a maintainer replies lgtmi on one of your issues, your future issues will stay open. If a maintainer replies lgtm, your future issues and PRs will stay open.
The problem here is that we definitely want the shrinkwrap for the CLI install but it probably is annoying for using the package as a library.
I came across this discussion on shrinkwrap which implies that the npm ecosystem does not really use shrinkwraps and we should use bundleDependencies instead? npm/cli#8938
I'm going to leave this open for the moment to make a better decision here. My default move for now would be to add integrity.
changed the title [-]Duplicate @earendil-works/pi-ai install splits the API provider registry (pi-coding-agent ships npm-shrinkwrap.json with no integrity)[/-][+]Move off shrinkwrap. Was Duplicate @earendil-works/pi-ai install splits the API provider registry (pi-coding-agent ships npm-shrinkwrap.json with no integrity)[/+]on Jun 12, 2026
If we move to bundledDependencies then I think we inherit this problem. A library user will still have both libraries in the tree twice. Once hoisted and once toplevel.
Proposal: move off npm-shrinkwrap.json, but do not bundle any @earendil-works/* packages. Instead, each published Pi package should bundle only its third-party runtime dependency closure with bundledDependencies, while keeping Pi packages as normal npm dependencies.
Concretely:
@earendil-works/pi-coding-agent keeps @earendil-works/pi-ai, @earendil-works/pi-agent-core, and @earendil-works/pi-tui as regular dependencies, not bundled.
Published Pi packages use exact lockstep @earendil-works/* dependency versions, so the CLI install gets the intended internal package set without shrinkwrap.
Third-party runtime deps are bundled from a staged publish directory generated from the reviewed root lockfile.
CI/release validation checks that no @earendil-works/* package appears in bundledDependencies, all bundled third-party versions match the lockfile, no local/workspace refs leak into the tarballs, and install-script allowlisting remains explicit.
Benefits:
Fixes the registry split from this issue for the common case: npm can dedupe @earendil-works/pi-ai, so user code and the agent loop share the same module instance and provider registry.
Compatible with npm 12 removing shrinkwrap support.
Preserves most of the installer safety property: install.sh can keep --ignore-scripts, and third-party dependency code comes from reviewed, pinned bundled contents instead of being resolved dynamically during install.
Keeps the package usable as a library because Pi packages remain visible to npm's normal resolver/dedupe behavior.
Risks / tradeoffs:
Tarballs get larger, and third-party deps may be duplicated across Pi packages unless we are careful about package boundaries.
Bundled third-party deps cannot be deduped or overridden by consumers in the usual way; security fixes require republishing the affected Pi package.
If a consumer intentionally installs incompatible Pi package versions, npm can still create duplicate @earendil-works/pi-ai copies. Exact lockstep deps reduce accidental drift but do not make mixed-version installs impossible.
Workspace hoisting means plain npm pack is not enough; release must publish from a staged directory or otherwise materialize the bundled third-party node_modules tree.
Optional/platform dependencies need explicit validation so the bundle built on one OS still includes the supported platform artifacts.
Separately, it would still be worth making the pi-ai provider registry resilient to duplicate installs, e.g. via globalThis/Symbol.for(...) or explicit registry injection. The bundling policy should avoid causing duplicate Pi packages, but the registry should not fail catastrophically if a consumer's dependency graph does.
changed the title [-]Move off shrinkwrap. Was Duplicate @earendil-works/pi-ai install splits the API provider registry (pi-coding-agent ships npm-shrinkwrap.json with no integrity)[/-][+]Move off Shrinkwrap[/+]on Jun 13, 2026
I'm complete at a loss of what to do here to be honest. It might be that we just cannot rely on npm at all for what we're doing and we just need to accept two things:
npm install @earendil-works/pi-coding-agent is just for library usage. For application usage itself we need to publish tarballs with bundled resources we can npm install instead. Maybe under a second package called @earendil-works/pi-coding-agent-bundled. But the ramifications of this are pretty wild.
+1 — hitting the same bug on 0.79.3 with a custom api registered through
registerApiProvider. Adding one observation: failure is intermittent across
teammates installing from the same package-lock.json. Both pi-ai copies
exist on disk on every machine, but whether createAgentSession reads from
the hoisted or nested copy seems to depend on install order / cache state,
which makes the bug look "works on my machine" — and bisecting harder.
Local workaround that fully resolves it: an npm overrides entry
collapsing pi-ai to a single copy. Not a long-term answer (defeats the
hardening intent), but unblocks SDK users while a real fix lands. Happy to
test a PR.
To me the "two pi-ai copies / two API provider registries" problem sounds like a singleton dependency issue similar to how it is often the case with e.g. React.
Idea: make @earendil-works/pi-ai a peerDependency of packages that need to share its module-level registry (rather than allowing those packages to install their own private copy).
@earendil-works/pi-coding-agent would declare @earendil-works/pi-ai as a peer dependency, with a matching dev dependency for local development/builds.
This would make the consuming project provide the pi-ai instance, so calls to registerApiProvider() and the agent loop resolve to the same package instance.
(FYI @earendil-works/pi-agent-core also depends on @earendil-works/pi-ai, and pi-coding-agent depends on pi-agent-core, so to fully avoid a nested pi-ai, pi-agent-core may need the same change as well)
This is similar to how React packages usually specify react as a peer dependency because React relies on singleton module identity for hooks/context/etc.
One consequence: the install docs should probably ask users to install both packages explicitly, because not every package manager/configuration auto-installs peers:
The problem with the peer dependencies is that in theory this should apply to the internal provider libraries as well (eg: the openai one) and because of how npm and others are handling them, you end up installing pi-ai, pi-agent-core, pi-coding-agent whenever you want pi.
A new release-blocking instance of this shrinkwrap problem is now reproducible across @earendil-works/pi-coding-agent@0.84.4 through current 0.99.2. The published shrinkwrap pins nested brace-expansion@5.0.9; current advisories require >=5.0.12:
minimatch@10.2.6 permits brace-expansion@^5.0.8, but consumer overrides, direct 5.0.12 pins, npm update, npm dedupe, and npm audit fix cannot displace the nested shrinkwrapped 5.0.9. 0.99.2 has patched undici@8.10.2, leaving this as the sole full-tree audit finding in our release gate.
Could the next pi-coding-agent release either remove the published shrinkwrap as planned or regenerate it with brace-expansion@5.0.12+? We cannot safely publish by accepting the advisory or falsifying consumer lock metadata.
Another consequence of the shrinkwrap, for anyone consuming pi as a library: it pins known-vulnerable transitive versions that consumers cannot override. npm overrides don't reach inside a dependency's npm-shrinkwrap.json. We checked this with both a scoped and a global override.
Trivy (--severity HIGH,CRITICAL --ignore-unfixed) over a bare install of each release:
So a project whose release gate refuses fixable HIGHs can't take 0.78.1, the release that fixes GHSA-jfgx-wxx8-mp94, without accepting new findings it has no way to fix. Dropping the shrinkwrap, as this issue proposes, would let consumers resolve those normally. Failing that, refreshing it (brace-expansion ≥ 5.0.11) would clear today's set.
Downstream data point for this, from opencode-codebase-index (we consume @earendil-works/pi-coding-agent as a dev dependency for the Pi extension types).
The shrinkwrap keeps a vulnerable brace-expansion on disk that we cannot remediate from our side:
npm ls brace-expansion → @earendil-works/pi-coding-agent@0.99.1 > minimatch@10.2.6 > brace-expansion@5.0.9
Root overrides: { "brace-expansion": "5.0.12" } is ignored — tested in flat ("brace-expansion"), nested ("@earendil-works/pi-coding-agent": { ... }) and version-qualified ("@earendil-works/pi-coding-agent@0.99.1": { ... }) forms against the published tarball; all leave the nested copy at 5.0.9. npm audit fix cannot reach it either.
Unpacking package/npm-shrinkwrap.json from the published tarballs shows brace-expansion@5.0.9 in 0.99.1, 0.99.2 and 1.0.0 (our latest resolution).
What makes this cheap to fix upstream: the shrinkwrapped minimatch@10.2.6 already declares brace-expansion: "^5.0.8", so a plain npm-shrinkwrap.json regeneration — no dependency change, no code change — resolves 5.0.12 and clears GHSA-q2hr-2g5m-vwhr, GHSA-qhr7-859c-m2p7 and GHSA-6j4f-fj2g-mc7p. The same regen already picked up the undici 8.10.2 bump that arrived with 0.99.x, so the mechanism works.
We are not gating on it (no npm audit in our CI; the copy is dev-only and never reaches consumers of our published package), so we dismissed our Dependabot alert as tolerable risk with a pointer upstream. Tracking on our side: Helweg/open-codebase-index#379
The shrinkwrap is gone with the next release. @earendil-works/pi-coding-agent on npm no longer pins transitive dependencies, so library consumers can dedupe and override them normally.
Pinned installs are now only available through the pi.dev installer (curl -fsSL https://pi.dev/install.sh | sh, or install.ps1 on Windows), which installs from a reviewed lockfile and updates with pi update. Running it over an existing global npm install migrates it.
pi update on a global npm install now recommends running the installer after updating. npm self-updates still pass --min-release-age=0, because otherwise a configured npm age gate would refuse the freshly advertised pi version. Since npm has no per-package age gate, that flag also applies to transitive dependencies, which is one more reason to move to the managed install.
Worth mentioning here. --min-release-age=0 gets picked up by corporate security tooling and causes pi update to straight-up crash the terminal emulator. Which means Pi cannot be updated at all now.
Activity
github-actions commentedon Jun 11, 2026
This issue was auto-closed. All issues from new contributors are auto-closed by default.
Maintainers review auto-closed issues daily and reopen worthwhile ones. Issues that do not meet the quality bar in CONTRIBUTING.md will not be reopened or receive a reply.
If a maintainer replies
lgtmion one of your issues, your future issues will stay open. If a maintainer replieslgtm, your future issues and PRs will stay open.See CONTRIBUTING.md.
mitsuhiko commentedon Jun 12, 2026
The problem here is that we definitely want the shrinkwrap for the CLI install but it probably is annoying for using the package as a library.
I came across this discussion on shrinkwrap which implies that the npm ecosystem does not really use shrinkwraps and we should use
bundleDependenciesinstead? npm/cli#8938I'm going to leave this open for the moment to make a better decision here. My default move for now would be to add integrity.
mitsuhiko commentedon Jun 12, 2026
Well looks like shrinkwrap is going with npm 12 so we need to do something: npm/cli#9262
[-]Duplicate @earendil-works/pi-ai install splits the API provider registry (pi-coding-agent ships npm-shrinkwrap.json with no integrity)[/-][+]Move off shrinkwrap. Was Duplicate @earendil-works/pi-ai install splits the API provider registry (pi-coding-agent ships npm-shrinkwrap.json with no integrity)[/+]mitsuhiko commentedon Jun 12, 2026
If we move to
bundledDependenciesthen I think we inherit this problem. A library user will still have both libraries in the tree twice. Once hoisted and once toplevel.mitsuhiko commentedon Jun 12, 2026
AI slop generated proposal:
Proposal: move off
npm-shrinkwrap.json, but do not bundle any@earendil-works/*packages. Instead, each published Pi package should bundle only its third-party runtime dependency closure withbundledDependencies, while keeping Pi packages as normal npm dependencies.Concretely:
@earendil-works/pi-coding-agentkeeps@earendil-works/pi-ai,@earendil-works/pi-agent-core, and@earendil-works/pi-tuias regular dependencies, not bundled.@earendil-works/*dependency versions, so the CLI install gets the intended internal package set without shrinkwrap.@earendil-works/*package appears inbundledDependencies, all bundled third-party versions match the lockfile, no local/workspace refs leak into the tarballs, and install-script allowlisting remains explicit.Benefits:
@earendil-works/pi-ai, so user code and the agent loop share the same module instance and provider registry.install.shcan keep--ignore-scripts, and third-party dependency code comes from reviewed, pinned bundled contents instead of being resolved dynamically during install.Risks / tradeoffs:
@earendil-works/pi-aicopies. Exact lockstep deps reduce accidental drift but do not make mixed-version installs impossible.npm packis not enough; release must publish from a staged directory or otherwise materialize the bundled third-partynode_modulestree.Separately, it would still be worth making the
pi-aiprovider registry resilient to duplicate installs, e.g. viaglobalThis/Symbol.for(...)or explicit registry injection. The bundling policy should avoid causing duplicate Pi packages, but the registry should not fail catastrophically if a consumer's dependency graph does.This comment is AI slop generated.
[-]Move off shrinkwrap. Was Duplicate @earendil-works/pi-ai install splits the API provider registry (pi-coding-agent ships npm-shrinkwrap.json with no integrity)[/-][+]Move off Shrinkwrap[/+]mitsuhiko commentedon Jun 13, 2026
I'm complete at a loss of what to do here to be honest. It might be that we just cannot rely on npm at all for what we're doing and we just need to accept two things:
npm install @earendil-works/pi-coding-agentis just for library usage. For application usage itself we need to publish tarballs with bundled resources we can npm install instead. Maybe under a second package called@earendil-works/pi-coding-agent-bundled. But the ramifications of this are pretty wild.SyllaJay commentedon Jun 13, 2026
+1 — hitting the same bug on 0.79.3 with a custom api registered through
registerApiProvider. Adding one observation: failure is intermittent across
teammates installing from the same package-lock.json. Both pi-ai copies
exist on disk on every machine, but whether
createAgentSessionreads fromthe hoisted or nested copy seems to depend on install order / cache state,
which makes the bug look "works on my machine" — and bisecting harder.
Local workaround that fully resolves it: an npm
overridesentrycollapsing pi-ai to a single copy. Not a long-term answer (defeats the
hardening intent), but unblocks SDK users while a real fix lands. Happy to
test a PR.
patricktree commentedon Jun 14, 2026
To me the "two
pi-aicopies / two API provider registries" problem sounds like a singleton dependency issue similar to how it is often the case with e.g. React.Idea: make
@earendil-works/pi-aiapeerDependencyof packages that need to share its module-level registry (rather than allowing those packages to install their own private copy).@earendil-works/pi-coding-agentwould declare@earendil-works/pi-aias a peer dependency, with a matching dev dependency for local development/builds.This would make the consuming project provide the
pi-aiinstance, so calls toregisterApiProvider()and the agent loop resolve to the same package instance.(FYI
@earendil-works/pi-agent-corealso depends on@earendil-works/pi-ai, andpi-coding-agentdepends onpi-agent-core, so to fully avoid a nestedpi-ai,pi-agent-coremay need the same change as well)This is similar to how React packages usually specify
reactas a peer dependency because React relies on singleton module identity for hooks/context/etc.One consequence: the install docs should probably ask users to install both packages explicitly, because not every package manager/configuration auto-installs peers:
mitsuhiko commentedon Jun 14, 2026
The problem with the peer dependencies is that in theory this should apply to the internal provider libraries as well (eg: the openai one) and because of how npm and others are handling them, you end up installing
pi-ai,pi-agent-core,pi-coding-agentwhenever you want pi.14 remaining items
revazi commentedon Oct 1, 2026
A new release-blocking instance of this shrinkwrap problem is now reproducible across
@earendil-works/pi-coding-agent@0.84.4through current0.99.2. The published shrinkwrap pins nestedbrace-expansion@5.0.9; current advisories require>=5.0.12:minimatch@10.2.6permitsbrace-expansion@^5.0.8, but consumeroverrides, direct5.0.12pins,npm update,npm dedupe, andnpm audit fixcannot displace the nested shrinkwrapped5.0.9.0.99.2has patchedundici@8.10.2, leaving this as the sole full-tree audit finding in our release gate.Could the next
pi-coding-agentrelease either remove the published shrinkwrap as planned or regenerate it withbrace-expansion@5.0.12+? We cannot safely publish by accepting the advisory or falsifying consumer lock metadata.mitsuhiko commentedon Oct 1, 2026
Shrinkwrap will be removed with the next release.
pmerrison commentedon Oct 2, 2026
Another consequence of the shrinkwrap, for anyone consuming pi as a library: it pins known-vulnerable transitive versions that consumers cannot override. npm
overridesdon't reach inside a dependency'snpm-shrinkwrap.json. We checked this with both a scoped and a global override.Trivy (
--severity HIGH,CRITICAL --ignore-unfixed) over a bare install of each release:@earendil-works/pi-coding-agentSo a project whose release gate refuses fixable HIGHs can't take 0.78.1, the release that fixes GHSA-jfgx-wxx8-mp94, without accepting new findings it has no way to fix. Dropping the shrinkwrap, as this issue proposes, would let consumers resolve those normally. Failing that, refreshing it (brace-expansion ≥ 5.0.11) would clear today's set.
Helweg commentedon Oct 2, 2026
Downstream data point for this, from opencode-codebase-index (we consume
@earendil-works/pi-coding-agentas a dev dependency for the Pi extension types).The shrinkwrap keeps a vulnerable
brace-expansionon disk that we cannot remediate from our side:npm ls brace-expansion→@earendil-works/pi-coding-agent@0.99.1 > minimatch@10.2.6 > brace-expansion@5.0.9overrides: { "brace-expansion": "5.0.12" }is ignored — tested in flat ("brace-expansion"), nested ("@earendil-works/pi-coding-agent": { ... }) and version-qualified ("@earendil-works/pi-coding-agent@0.99.1": { ... }) forms against the published tarball; all leave the nested copy at 5.0.9.npm audit fixcannot reach it either.package/npm-shrinkwrap.jsonfrom the published tarballs showsbrace-expansion@5.0.9in 0.99.1, 0.99.2 and 1.0.0 (ourlatestresolution).What makes this cheap to fix upstream: the shrinkwrapped
minimatch@10.2.6already declaresbrace-expansion: "^5.0.8", so a plainnpm-shrinkwrap.jsonregeneration — no dependency change, no code change — resolves 5.0.12 and clears GHSA-q2hr-2g5m-vwhr, GHSA-qhr7-859c-m2p7 and GHSA-6j4f-fj2g-mc7p. The same regen already picked up theundici8.10.2 bump that arrived with 0.99.x, so the mechanism works.We are not gating on it (no
npm auditin our CI; the copy is dev-only and never reaches consumers of our published package), so we dismissed our Dependabot alert as tolerable risk with a pointer upstream. Tracking on our side: Helweg/open-codebase-index#379mitsuhiko commentedon Oct 2, 2026
The shrinkwrap is gone with the next release.
@earendil-works/pi-coding-agenton npm no longer pins transitive dependencies, so library consumers can dedupe and override them normally.Pinned installs are now only available through the pi.dev installer (
curl -fsSL https://pi.dev/install.sh | sh, orinstall.ps1on Windows), which installs from a reviewed lockfile and updates withpi update. Running it over an existing global npm install migrates it.pi updateon a global npm install now recommends running the installer after updating. npm self-updates still pass--min-release-age=0, because otherwise a configured npm age gate would refuse the freshly advertised pi version. Since npm has no per-package age gate, that flag also applies to transitive dependencies, which is one more reason to move to the managed install.This comment is AI-generated by
/wrfeat(coding-agent): remove npm shrinkwrap and recommend the managed i…
feat: install Pi via release lockfile and npm ci
refactor: assume local "locked" Pi install
fix(deps): update dependency @earendil-works/pi-coding-agent to v1.0.…
feat(coding-agent): remove npm shrinkwrap and recommend the managed i…
tkgalk commentedon Oct 22, 2026
Worth mentioning here.
--min-release-age=0gets picked up by corporate security tooling and causespi updateto straight-up crash the terminal emulator. Which means Pi cannot be updated at all now.