fix(ci): unblock the release — npm 11 esbuild bloat + two self-inflicted assertions
Lint / hadolint (push) Successful in 8s
Lint / skill-floor (push) Successful in 9s
Lint / doc-drift (push) Successful in 10s
Lint / actionlint (push) Successful in 19s
Publish Docker Image / lint-gate (push) Successful in 15s
Publish Docker Image / resolve-versions (push) Successful in 10s
Publish Docker Image / base-decide (push) Successful in 14s
Publish Docker Image / build-base (push) Has been skipped
Publish Docker Image / smoke (push) Successful in 5m4s
Publish Docker Image / smoke-studio (push) Successful in 13m17s
Publish Docker Image / build-variant (push) Successful in 33m49s
Publish Docker Image / update-description (push) Successful in 6s
Publish Docker Image / promote-base-latest (push) Successful in 14s
Publish Docker Image / build-variant-studio (push) Successful in 30m58s
Lint / hadolint (push) Successful in 8s
Lint / skill-floor (push) Successful in 9s
Lint / doc-drift (push) Successful in 10s
Lint / actionlint (push) Successful in 19s
Publish Docker Image / lint-gate (push) Successful in 15s
Publish Docker Image / resolve-versions (push) Successful in 10s
Publish Docker Image / base-decide (push) Successful in 14s
Publish Docker Image / build-base (push) Has been skipped
Publish Docker Image / smoke (push) Successful in 5m4s
Publish Docker Image / smoke-studio (push) Successful in 13m17s
Publish Docker Image / build-variant (push) Successful in 33m49s
Publish Docker Image / update-description (push) Successful in 6s
Publish Docker Image / promote-base-latest (push) Successful in 14s
Publish Docker Image / build-variant-studio (push) Successful in 30m58s
v1.9.0 was tagged but never published: smoke failed 90-passed/3-failed and
build-variant needs smoke, so nothing reached the registry. All three are fixed.
1. Size, 431 MB over threshold. Node 24 brings npm 11, which installs EVERY
@esbuild/<platform> optional binary instead of the matching one: 26 dirs,
284 MB per pi-coding-agent copy. Measured on pi-fork: npm 10.9.8 -> 165 MB
(exactly what v1.8.14 shipped), npm 11.19.0 -> 449 MB. npm 11 ignores the
os/cpu constraints AND --os/--cpu AND an npmrc carrying them, all measured,
so Dockerfile.variant prunes explicitly, keeping linux-$(node -p
process.arch) so one line is right on both arches. Pruned in the SAME layer
as each install, or the bytes survive in the earlier layer. Three sites:
global pi, pi-fork, pi-studio. Verified esbuild still transforms TS after.
2. om's node_modules assertion tested an npm artefact. om has zero runtime deps;
its node_modules held ONE file (.package-lock.json) and 20 empty scope dirs.
npm 11 stopped creating it. Now asserts the entry point pi actually loads,
read from package.json -> pi.extensions.
3. The skill-source annotation added in v1.9.0 ("baked (package copy)") broke
the assertion matching "baked$". Pattern now allows an optional suffix; which
copy shipped stays authoritatively asserted against the manifest + tree hash.
Also: a failed size check now prints the largest layers, largest directories and
an @esbuild sentinel, so this class attributes itself next time instead of
costing a CI dig plus a local npm bisect.
Threshold stays 3800 MB: it caught a real regression and raising it would have
thrown the signal away. No Dockerfile.base/rootfs change, so base-0fb1256c7f99
is reused and build-base is skipped.
This commit is contained in:
+79
-2
@@ -11,7 +11,78 @@ Pre-v1.0.0 tags followed the pi npm version (`v{pi_version}[letter]`).
|
||||
|
||||
---
|
||||
|
||||
## Unreleased
|
||||
## v1.9.1 — 2026-09-10
|
||||
|
||||
**v1.9.0 was tagged but never published: its own smoke gate stopped it, and it
|
||||
was right to.** `build-base` succeeded, then `smoke` failed 90-passed/3-failed,
|
||||
and because `build-variant` needs `smoke`, both variants, `promote-base-latest`
|
||||
and `update-description` were skipped. No image reached the registry, so
|
||||
`latest` still pointed at v1.8.14. v1.9.1 carries everything listed under v1.9.0
|
||||
below, plus the three fixes here. Two of the three failures were self-inflicted
|
||||
by v1.9.0's own changes, and the third was a real regression that the Node bump
|
||||
dragged in — which is the case for keeping the gate strict.
|
||||
|
||||
**Failure 1 — the image was 431 MB over its size threshold, and npm 11 was the
|
||||
cause.** Node 22 → 24 brings npm 10 → 11, and npm 11 installs **every**
|
||||
`@esbuild/<platform>` optional binary rather than only the one matching the host:
|
||||
26 platform directories covering aix-ppc64, android, darwin, freebsd, netbsd,
|
||||
openbsd, win32, s390x, riscv64 and more, none of which this image can execute.
|
||||
Measured on pi-fork's dependency tree, same repo and same command:
|
||||
|
||||
| npm | packages | `node_modules` |
|
||||
| --- | --- | --- |
|
||||
| 10.9.8 | 136 | **165 MB** |
|
||||
| 11.19.0 | 169 | **449 MB** |
|
||||
|
||||
The 165 MB figure reproduces exactly what v1.8.14 shipped, which is what
|
||||
identified npm rather than the image as the variable. esbuild declares those
|
||||
binaries with `os`/`cpu` constraints, but npm 11 ignores them — and also ignores
|
||||
`--os`/`--cpu` flags and an `.npmrc` carrying `os=`/`cpu=` (all three measured,
|
||||
all three still produced 26 directories). So `Dockerfile.variant` now prunes
|
||||
explicitly, keeping only `linux-$(node -p process.arch)` so one line is correct
|
||||
on amd64 and arm64. Verified this removes dead weight and not function: after
|
||||
pruning, `esbuild.transformSync` still compiles TypeScript. The prune runs in the
|
||||
**same layer** as each `npm install` — deleting in a later `RUN` would leave the
|
||||
bytes in the earlier layer and shrink the image by nothing. Three sites are
|
||||
covered: the global pi install, pi-fork, and pi-studio (which pulls its own
|
||||
pi-coding-agent copy), for roughly 548 MB recovered in the non-studio variant and
|
||||
822 MB in studio. The threshold stays at 3800 MB deliberately: it caught a real
|
||||
regression, and raising it to accommodate one would have discarded the signal.
|
||||
|
||||
**Failure 2 — the om `node_modules` assertion was checking an npm artefact, not
|
||||
the software.** `pi-observational-memory` declares **zero** runtime dependencies:
|
||||
8 devDependencies (omitted by `--omit=dev`) and 4 peerDependencies, which pi
|
||||
itself provides. npm 10 still materialised a `node_modules` for it, but that
|
||||
directory contained exactly **one file** (`.package-lock.json`, 4 KB) and no
|
||||
nested `package.json` — 20 empty scope directories. npm 11 stopped creating it,
|
||||
so `test -d node_modules` went red while nothing about om had changed or broken.
|
||||
The assertion now checks what must actually hold — that the entry point pi loads
|
||||
exists — read out of the manifest pi itself reads (`package.json` →
|
||||
`pi.extensions`) rather than a hardcoded path that could drift. pi-fork keeps its
|
||||
`node_modules` check, because pi-fork has real dependencies where the directory's
|
||||
absence would mean something.
|
||||
|
||||
**Failure 3 — the skill-source annotation broke the assertion that reads it.**
|
||||
v1.9.0 taught `pi-devbox-version` to say *which* pi-extensions copy shipped
|
||||
(`baked (package copy)`, or a loud FALLBACK/MIXED marker). The smoke assertion
|
||||
matched `^ $s +baked$`, anchored at the end, so the annotation failed it even
|
||||
though the state reported was correct. The pattern now allows an optional
|
||||
` (...)` suffix, matched loosely on purpose: *which* copy shipped is already
|
||||
asserted authoritatively against the manifest field and its measured tree hash,
|
||||
and re-encoding that wording in a second regex would just add a second place to
|
||||
update. The lesson recorded rather than the fix alone: the display branches were
|
||||
tested in an isolated harness that passed, but the assertion **consuming** them
|
||||
was never run — harness-passes-therefore-consumer-passes was an assumption.
|
||||
|
||||
**A red size assertion now carries its own diagnostic.** Attributing the 431 MB
|
||||
took a full CI-log dig plus a local npm bisect, while the container knew where
|
||||
its bytes were the whole time. On failure the check now prints the largest
|
||||
layers, the largest directories, and a count of `@esbuild` platform directories
|
||||
as a sentinel for this exact regression recurring — the same principle the `run()`
|
||||
helper already applies to every other assertion.
|
||||
|
||||
No `Dockerfile.base` or `rootfs/` change, so the base fingerprint is untouched
|
||||
and `base-decide` reuses `base-0fb1256c7f99` built during the v1.9.0 attempt.
|
||||
|
||||
**A gate for documentation drift, because five claims rotted in one release and
|
||||
one of them was published.** Preparing v1.9.0 turned up a cluster of stale
|
||||
@@ -94,7 +165,13 @@ is stale (a live client shows 45) but left alone rather than corrected on a
|
||||
guess, since that count cannot be attributed to the baked 3.9.0 server without
|
||||
measuring it.
|
||||
|
||||
## v1.9.0 — 2026-09-10
|
||||
## v1.9.0 — 2026-09-10 (tagged, never published — superseded by v1.9.1)
|
||||
|
||||
> This tag exists in git but no image was ever pushed for it: `smoke` failed
|
||||
> three assertions and skipped every downstream job. Everything below ships in
|
||||
> **v1.9.1**, whose entry explains the three failures and their fixes. Kept as
|
||||
> its own section rather than folded away, because the tag is real and someone
|
||||
> will eventually find it and wonder why Docker Hub has no v1.9.0.
|
||||
|
||||
**`shellcheck` is now in the image, because the release gate it depends on could
|
||||
not be run by anyone.** v1.8.14 made shell lint a release gate: `scripts/lint-shell.sh`
|
||||
|
||||
Reference in New Issue
Block a user