Skip to content

Docker image dependency tree is resolved fresh at build time, not from the published shrinkwrap #1960

Description

@heskew

SummaryThe Dockerfile installs harper from a locally built tarball:```

npm install --ignore-scripts --global harper-.tgz
npm decides whether to honour a package's bundled `npm-shrinkwrap.json` from the `_hasShrinkwrap` flag in the **registry packument** — metadata the registry sets at publish time. A local tarball has no packument, so npm never learns the shrinkwrap exists: it opens the tarball, reads `package.json`, and resolves the whole tree fresh.## EvidenceThe *same* published `harper@5.1.23` artifact, installed two ways:| | shrinkwrap pin | via registry | via local tarball | latest on npm | |---|---|---|---|---| | fastify | 5.8.5 | 5.8.5 | **5.10.0** | 5.10.0 | | chokidar | 4.0.3 | 4.0.3 | 4.0.3 | 5.0.0 | | graphql | 16.14.2 | 16.14.2 | 16.14.2 | 17.0.2 |fastify is the discriminator — the tarball install resolved to latest rather than the pin. (chokidar and graphql match only coincidentally: harper's `^4.0.3` / `^16.10.0` ranges cap below those new majors.) The registry install produced exactly 794 packages, matching the shrinkwrap's 794 entries, in an isolated nested tree.## Why it matters1. **The image is not reproducible.** Two builds of the same harper commit, weeks apart, can ship different dependency trees — whatever is newest on npm within our semver ranges at build time. 2. **The image does not match what npm consumers get.** Registry consumers install the pinned tree; the image installs something else. 3. It is why the image still carries the react-native tree after #1937's shrinkwrap prune (see #1959) — that fix reaches registry installs but not this one.## DirectionMake the image install honour the shrinkwrap. Extracting the tarball and installing in place works — the shrinkwrap is then present as the lockfile:
tar -xzf harper-
.tgz --strip-components=1 -C

&& cd && npm install --omit=dev --ignore-scripts
```Measured locally (macOS, like-for-like): 444M -> 268M with #1959's prune in place, and the tree becomes pinned. npm ci would be exact rather than merely shrinkwrap-guided, but currently trips the sync check because the published `package.json` retains `devDependencies` while the shipped shrinkwrap has them pruned.Open questions for whoever picks this up:

  • Extract-and-install vs. keeping npm install --global and accepting an unpinned image.
  • Whether to make npm ci viable (strip devDependencies from the packed package.json), which would give an exactly-pinned image.
  • Bin/symlink placement and which harper — the entrypoint depends on it, including the HARPER_RUNTIME=bun path.Depends on fix(build): strip the react-native tree from the published shrinkwrap #1959: honouring an unpruned shrinkwrap would pin the react-native tree in, so this should land after it.

🤖 Generated with Claude Code

Activity

  1. self-assigned this
    on Jul 27, 2026
  2. added this to the v5.2 milestone on Aug 6, 2026
  3. added theissue type on Aug 6, 2026
  4. kriszyp commented on Aug 6, 2026

    @kriszyp
    Member

    Checked whether this has actually bitten a published image, since #2043 raised the possibility that the unpinned resolve picked up @harperfast/rocksdb-js 2.6.x. Short version: the mechanism is confirmed live on the shipped 5.2.0 artifact, but no published image was broken by it.

    The mechanism reproduces on the released tarball. Installing harper@5.2.0 the way the Dockerfile does — from a local .tgz, so npm never sees a packument and never learns the bundled shrinkwrap exists:

    bundled npm-shrinkwrap.json pins:  @harperfast/rocksdb-js  2.5.0
    fresh resolve from the tarball:    @harperfast/rocksdb-js  2.7.0
    

    Both v5.2.0 Dockerfiles still use that path (npm install --ignore-scripts --global harper-*.tgz in harper, npm install --global harperfast-harper-pro-*.tgz in harper-pro), so every image build resolves ^2.5.0 to whatever is latest at build time. The relevant publish dates: 2.6.0 and 2.6.1 on 2026-07-31, 2.7.0 on 2026-08-04 — all inside the ^2.5.0 range, all after the shrinkwrap was written.

    But the 5.2.0 image is fine. The Release Harper Pro to Environments run of 2026-08-01T02:22 (immediately after the 5.2.0 tag) is green end to end, including the stages that would have caught a broken table open:

    • Build Docker image (v5.2.0) — success
    • Deploy to stage hosts — success
    • Health check stage — success
    • Smoke test — create/verify cluster (v5.2.0) — success

    A cluster was created and verified against that image, so whatever rocksdb-js it resolved at build time opens tables correctly. Nothing needs rebuilding, and the fleet isn't exposed today.

    What that leaves. The exposure isn't a broken image, it's that the image's dependency tree is decided by publish timing rather than by anything we tested. Two consequences worth separating:

    1. Every rebuild of the same tag can produce a different tree. harper:5.2.0 built today resolves 2.7.0; built on 2026-08-01 it resolved something else. That's a reproducibility hole in a released artifact, independent of whether any particular resolve is broken.
    2. The next incompatible publish inside a caret range lands in the image with no Harper change to review. This time the stage smoke test would have caught a total failure — it wouldn't necessarily catch a subtler incompatibility, and it runs after the image is already built and tagged.

    Keeping this at P1 on that basis rather than escalating: real and structural, but not currently breaking anything. #2042 remains the fix.

    — KrAIs (Claude Opus 5)

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

Fields

Priority

P1

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions