Skip to content

Move off Shrinkwrap #5653

Closed
Closed

Description

@yoyofield

What happened?

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.

Version

0.79.1

Activity

  1. github-actions commented on Jun 11, 2026

    @github-actions
    Contributor

    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.

    See CONTRIBUTING.md.

  2. self-assigned this
    on Jun 12, 2026
  3. mitsuhiko commented on Jun 12, 2026

    @mitsuhiko
    Member

    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.

  4. mitsuhiko commented on Jun 12, 2026

    @mitsuhiko
    Member

    Well looks like shrinkwrap is going with npm 12 so we need to do something: npm/cli#9262

  5. 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
  6. mitsuhiko commented on Jun 12, 2026

    @mitsuhiko
    Member

    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.

  7. mitsuhiko commented on Jun 12, 2026

    @mitsuhiko
    Member

    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 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.

    This comment is AI slop generated.

  8. 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
  9. mitsuhiko commented on Jun 13, 2026

    @mitsuhiko
    Member

    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.

  10. SyllaJay commented on Jun 13, 2026

    @SyllaJay

    +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.

  11. patricktree commented on Jun 14, 2026

    @patricktree

    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:

    npm install @earendil-works/pi-coding-agent @earendil-works/pi-ai
    pnpm add @earendil-works/pi-coding-agent @earendil-works/pi-ai
  12. mitsuhiko commented on Jun 14, 2026

    @mitsuhiko
    Member

    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.

  13. 14 remaining items

  14. revazi commented on Oct 1, 2026

    @revazi

    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.

  15. mitsuhiko commented on Oct 1, 2026

    @mitsuhiko
    Member

    Shrinkwrap will be removed with the next release.

  16. pmerrison commented on Oct 2, 2026

    @pmerrison

    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:

    @earendil-works/pi-coding-agent fixable HIGH from the shrinkwrap
    0.78.1 13 undici 8.3.0, protobufjs 7.5.9, brace-expansion 5.0.6, ws 8.20.1
    0.79.10 8 brace-expansion 5.0.6 (×5 CVEs), undici 8.5.0 (×3)
    0.99.2 / 1.0.0 2 brace-expansion 5.0.9 (CVE-2026-102276, CVE-2026-102278; fixed in 5.0.11)

    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.

  17. Helweg commented on Oct 2, 2026

    @Helweg

    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

  18. mitsuhiko commented on Oct 2, 2026

    @mitsuhiko
    Member

    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.

    This comment is AI-generated by /wr

  19. added a commit that references this issue on Oct 2, 2026
    581e7ba
  20. added a commit that references this issue on Oct 8, 2026
    2632bbd
  21. tkgalk commented on Oct 22, 2026

    @tkgalk

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions