Skip to content

RFC: Content-Addressable Store#912

Open
manzoorwanijk wants to merge 1 commit into
npm:mainfrom
manzoorwanijk:rfc-content-addressable-store
Open

RFC: Content-Addressable Store#912
manzoorwanijk wants to merge 1 commit into
npm:mainfrom
manzoorwanijk:rfc-content-addressable-store

Conversation

@manzoorwanijk

@manzoorwanijk manzoorwanijk commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an RFC for a global, file-level content-addressable store (CAS) for npm. node_modules is materialized from the store by copy-on-write clone or hardlink (with copy as fallback) instead of re-extracting a tarball for every package in every project. The store is a materialization backend, not an install strategy: it composes with hoisted, nested, shallow, and linked, and introduces a store-dir config, a package-import-method config (auto = clone → hardlink → copy, pnpm's semantics), and an npm store command. No changes to package-lock.json, the registry, or npm publish.

Motivation

npm's cache is already content-addressed and integrity-verified — but at tarball granularity, and only as a download cache: every install re-runs tar.x per package (reify.jspacote.extract). npm has never hardlinked or cloned a file into node_modules.

Measured on Gutenberg (2.1 GB, 102k files, macOS/APFS): five worktrees cost ~11.2 GB today vs ~1.8 GB materialized from a CAS, with the marginal worktree at ~40 MB instead of ~2.2 GB. Across two real projects, 37% of node_modules files are byte-identical duplicates. Warm-cache npm ci still gunzips and writes every byte; with the store it creates directories and links.

Notable semantics

  • Blobs are keyed on (contentHash, mask) — the exec bit comes from both isPackageBin and tar-header modes, so content alone is not a sufficient key. Blobs are stored read-only.
  • Hardlinked files become read-only (EACCES on in-place writes — loud failure instead of pnpm's silent cross-project corruption); clone/copy keep today's modes bit-for-bit. Restrictive-umask installs fall back to clone-or-copy automatically.
  • Packages that may run lifecycle scripts (RFC 0054 allowlist, plus --dangerously-allow-all-scripts incl. binding.gyp synthesis) are never hardlinked.
  • Git dependencies are excluded — no stable content key exists, by design of RFC 0048.
  • Cross-filesystem installs provision a per-filesystem store (pnpm's per-drive behaviour) rather than silently degrading to copy.
  • Resolves npm/cli#7608 (tar Link entries silently dropped) and npm/cli#5951 (hardlink-unsupported filesystems).

A separate proposal, RFC: Global Virtual Store for Linked Installs (#911), covers a symlinked machine-global virtual store for linked installs; the two share a store root layout but neither depends on the other.

See the RFC for the full design, alternatives, implementation plan, tests, and unresolved questions.


Disclosure: Claude Code was used to draft this PR description and the initial version of this RFC, and to iterate on it — including adversarial review rounds against the npm CLI source with Codex — before submission.

@manzoorwanijk
manzoorwanijk force-pushed the rfc-content-addressable-store branch from 3538ca2 to d2b518b Compare July 11, 2026 11:40
@manzoorwanijk
manzoorwanijk marked this pull request as ready for review July 11, 2026 11:43
@manzoorwanijk
manzoorwanijk requested review from a team as code owners July 11, 2026 11:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] npm install 'my-package' silently drops extra hard links to the same inode in node_modules/my-package

1 participant