The mirrored LGP homepage prerenders / and reads manifest.yaml at build
time; these 3 baselines use an explicit COPY of config files that omitted
manifest.yaml, breaking SSG export (same fix as pydantic-ai in #5125). The
other 6 baselines copy the dir wholesale and already include it.
The previous push (cffb6547a) and lockfile-regen push (65a26ebc7)
did not appear to trigger Showcase: Build Check (PR) — the workflow
last ran on e7dcd3cf (the diagnostic-probe commit) and no subsequent
run is visible via `gh api .../actions/runs?head_sha=...`. The PR
checks page therefore still reflects the old strands failure with
the bad lockfile, even though that lockfile has been regenerated.
This commit:
1. Adds a one-line comment to the strands Dockerfile pointing at the
lockfile-regen commit, so a future reader can find the context
if Depot ever poisons that cache again.
2. Forces Showcase: Build Check (PR) to fire by changing a file the
workflow's paths filter (`showcase/**`) matches.
No behavioural change — the comment is dropped from the final image
by Docker's normal handling, and the file content the build sees is
the same `FROM node:22-slim AS frontend` it always was.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two independent fixes:
1. strands package-lock.json was invalid JSON.
Commit 00ce3a933 on main ("chore: ratchet showcase baseline to 95 +
sync strands lockfile", May 19) produced a lockfile with trailing
commas before closing braces — Node's V8 JSON parser (which npm
uses internally) rejects it as "Expected double-quoted property
name in JSON at position 1042" the moment `npm ci` tries to read
it. npm surfaces this as "command can only install with an
existing package-lock.json with lockfileVersion >= 1", which is
misleading — the lockfile exists and declares lockfileVersion: 3,
but it fails to parse before npm gets that far.
The first strands `Showcase: Build & Push` run on main after that
commit (2026-05-19T20:57:40Z) failed for the same reason; main's
strands check has been broken since, but B&P runs are gated by
paths-filter so subsequent commits that didn't touch
`showcase/integrations/strands/**` simply skipped the strands job
instead of failing. Our PR's `Showcase: Build Check (PR)` matrix
re-runs strands on every PR push and surfaces the inherited
breakage.
Fix: delete the malformed lockfile and regenerate with
`npm install --legacy-peer-deps --package-lock-only` against the
existing package.json. The new file is valid JSON (verified with
`node -e "JSON.parse(...)"`) and `npm ci` succeeds locally with
it. Lockfile size dropped from 849577 to 491930 bytes — the prior
sync had bloated entries on top of being malformed.
Also reverts the Dockerfile probe and the split-COPY workaround
added in earlier commits on this branch (e82a938a0, b56a9252d,
e7dcd3cff). The probe was the right diagnostic — it printed the
first 200 bytes of /app/package-lock.json and showed only
"lockfileVersion: 3," before parse error, which pointed at the
malformed JSON. With a valid lockfile, `COPY ... && npm ci` works
on the simple Dockerfile shape and the workaround is no longer
needed.
2. setup-concept.test.ts path-traversal test had a /tmp race.
The fix landed in e82a938a0 wrote a decoy file via
`path.dirname(tmp)` — which resolves to the system temp root
(`/tmp` on Linux, `/var/folders/.../T` on macOS), not a per-test
scratch dir. Two concurrent runs of the test (e.g.
`vitest --watch` re-firing mid-edit, or a developer running tests
in two terminals) would race on the same shared decoy path; the
second's finally-cleanup could delete the first's decoy mid-test
and mask a real path-traversal regression.
Fix: mkdtemp a per-test `scratch` directory in beforeEach, nest
`tmp` inside it, plant the decoy in `scratch`, and let afterEach's
recursive rmSync of `scratch` handle cleanup. Removes the
try/finally block entirely. Comment math also corrected (the test
walks four `..` segments, not three).
Call-site enumeration:
- Dockerfile: only the `Showcase: Build & Push` and `Build Check
(PR)` workflows invoke this. Same `COPY ... && npm ci` shape as
every other integration Dockerfile.
- package-lock.json: consumed by `npm ci` only. New file generated
by npm itself from the same package.json the previous lockfile
targeted.
- setup-concept.test.ts: no external consumers; helper variables
`scratch`/`tmp` are module-local.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous probe confirmed the lockfile is present in /app/ at
849KB and `test -s` passes. npm ci then immediately errors with
EUSAGE saying "command can only install with an existing
package-lock.json with lockfileVersion >= 1" — even though the file
clearly exists.
This commit prints additional state so the next failed run gives us:
- node + npm versions (rules out older npm rejecting lockfileVersion 3)
- the first 200 bytes of the lockfile (confirms content isn't
corrupted / BOM / different encoding)
- the lockfileVersion parsed from JSON (confirms it's >= 1)
If npm ci still fails after this, the printed state will pinpoint
the exact divergence. Probe to be removed once root cause is known.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Prior attempts (lockfile sync from main d38265bf7, splitting COPY into
two lines e82a938a0) did not unstick the strands build — the COPY
step reports success while the subsequent npm ci fails immediately
with EUSAGE (no package-lock.json), pointing at a Depot remote
BuildKit cache layer that surfaces with only package.json present.
This commit:
1. Switches the COPY back to the `COPY package*.json ./` glob form
(changes cache key vs. the two-line split that failed).
2. Adds a probe RUN that `ls`-es /app and asserts package-lock.json
is non-empty before invoking npm ci. If the file is missing,
the probe fails loudly with a clear message instead of npm's
opaque EUSAGE output.
3. Fuses the assertion + npm ci into a single RUN so any future
cache replay must include both — partial cache hits can no
longer surface only the COPY layer.
If this still fails after push, the probe output ("package-lock.json
missing or empty in build context") tells us definitively whether
the cache is dropping the file or whether npm ci has some other
quarrel. Either way we'll have a concrete next step instead of
re-running the same opaque error.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three independent fixes from CR Round 1 partition (bucket a):
- framework-overview.tsx: handleCopyCommand never awaited
navigator.clipboard.writeText. A failed write (non-secure context,
unfocused tab, permission denied) would still flip the "Copied!"
indicator, so the user pastes nothing or stale content thinking the
copy succeeded. Now awaits, branches on rejection, and logs.
- setup-concept.test.ts: the path-traversal-via-concept-arg test
exercised the wrong code path. `concept = "../../secrets"` was
normalized by path.join *before* reaching resolveWithinDir
("docs/setup/../../secrets.mdx" -> "secrets.mdx"), so the test
passed because the decoy file didn't exist at the resolved location
rather than because the path-traversal defense fired. The test
would still pass if resolveWithinDir were deleted entirely.
Reworked to use a 4-level traversal whose normalized form actually
escapes integrationsRoot, and placed the decoy at the parent dir
so a successful escape would resolve to a real file - the test now
fails loudly if resolveWithinDir is removed.
- strands/Dockerfile: split `COPY package.json package-lock.json ./`
into two explicit COPY lines to bust a poisoned Depot remote
BuildKit cache entry on this branch. The poisoned layer surfaces
with only package.json present, breaking `npm ci`. Lockfile sync
from main (d38265bf7) wasn't enough since the cache key still
matches the single-line instruction string. Splitting changes the
instruction string and forces a fresh layer computation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lockfiles committed in 8ba692c42 were generated inside the monorepo
while pnpm's hoisted node_modules tree was present. npm-arborist
resolved transitive deps against pnpm's symlinks and wrote ~40
`../../../node_modules/.pnpm/...` paths into each lockfile's
`packages` map.
npm 10 can parse the JSON, but its arborist bombs out walking the
tree at those pnpm-relative entries with the misleading error:
npm error code EUSAGE
npm error The `npm ci` command can only install with an
npm error existing package-lock.json or npm-shrinkwrap.json
npm error with lockfileVersion >= 1.
`npm install --dry-run` surfaces the real cause:
Cannot read properties of undefined (reading 'extraneous')
A fresh lockfile generated in an isolated container works.
- broken: 1259 packages, 43 with `../../../node_modules/.pnpm/...`
- fresh: 1321 packages, all `node_modules/...` paths
This commit regenerates every integration's lockfile inside an
isolated `node:22-slim` container via `npm install
--package-lock-only --legacy-peer-deps` and verifies with `npm ci`.
Glob form 'COPY package*.json ./' didn't fix CI -- only package.json
ended up in /app, despite the build context transferring 1.38 MB
(lockfile is 705 KB so it's clearly in the source).
This commit:
1. Splits the COPY into two unambiguous lines.
2. Adds a 'RUN ls -la /app/' probe before npm ci.
If the probe shows package-lock.json present in /app, the issue is in
npm ci discovery. If absent, the issue is in build context upload.
Probe to be reverted once root cause is known.
CI failed on the 16 integrations whose explicit two-file COPY
`COPY package.json package-lock.json ./` hit a poisoned Depot remote
BuildKit cache entry: the cached layer reported CACHED but only
contained `package.json`, so the subsequent `npm ci` failed with
"command can only install with an existing package-lock.json".
Depot's cache had a layer indexed against the prior `COPY package.json
./` instruction; the new two-file instruction was matching it by some
internal cache-key collision. Two of 18 integrations (langgraph-python,
langgraph-typescript) passed only because they had a fully-cached
`RUN npm ci` layer from a sibling build that short-circuited the
broken COPY.
The glob form `COPY package*.json ./` produces an instruction string
that has never appeared in Depot's cache, so the layer is computed
fresh against the actual build context and includes both files. It
also reads cleaner than the explicit two-file enumeration.
No-Op when no cache poisoning is present -- the glob expands to exactly
package.json and package-lock.json on every integration (verified
locally; only those two files match per directory).
## Root cause
17 of 18 integration Dockerfiles copy `package.json` but NOT
`package-lock.json`, then run `npm install --legacy-peer-deps`. Despite a
~700KB lockfile sitting in every directory, none of them are consulted at
build time. Only `built-in-agent` was already doing it right.
Effect on Windows / WSL2:
1. `npm install` re-resolves package versions from scratch on every
rebuild, downloading ~1.1 GB into the build container's writable layer
plus ~hundreds of MB of `~/.npm/_cacache` that lives in the same
layer (BuildKit can't dedupe across builds because the layer hash
varies with each non-deterministic resolution).
2. The npm install layer's BuildKit cache key is just `package.json`'s
hash + base image — but with `npm install` (not `npm ci`) the install
itself is non-deterministic, so a cached layer that resolved
successfully can produce different node_modules trees than a fresh
resolution. Worse, intermediate state from interrupted rebuilds
(e.g. host OOM during `npm install`) is not reclaimed by `docker
builder prune` until 24h later.
3. WSL2's `docker_data.vhdx` grows monotonically — it never shrinks
until `wsl --shutdown` + `Optimize-VHD`. Repeated rebuilds compound
into a VHDX that can reach hundreds of GB on the Windows host
filesystem before any reclaim happens.
## Fix
Two-part:
1. **Lockfile-pinned, deterministic install** in all 18 Dockerfiles:
```
COPY package.json package-lock.json ./
RUN npm ci --legacy-peer-deps
```
- `npm ci` is faster, deterministic, and writes ~half the temporary
state of `npm install`.
- The lockfile in COPY makes the install layer's BuildKit cache key
stable across rebuilds, so once the layer is warm it actually stays
warm.
- Matches the pattern `built-in-agent` already uses.
2. **Reclaim dangling BuildKit cache in `bin/showcase build`** with a
24h-window `docker builder prune --filter "until=24h"`. Keeps the
warm cache for day-of work, reaps orphans from interrupted builds.
## Verification
```
for d in showcase/integrations/*/; do
grep -E "^(COPY package|RUN npm)" "$d/Dockerfile" | head -2
done
```
now prints identical:
```
COPY package.json package-lock.json ./
RUN npm ci --legacy-peer-deps
```
for every integration.
## Out of band (cannot land in this PR)
- `docker volume prune -af` -- one-time recovery, ran locally, reclaimed
16.11 GB from 236 anonymous Postgres volumes dating back to 2023.
- `Optimize-VHD` to compact the WSL2 docker_data.vhdx -- requires elevated
PowerShell after `wsl --shutdown`. Each developer runs this themselves
when their host drive gets tight; not something CI or this script can
do.
Replace sys.path.insert hacks in Python agent files with direct
imports via symlinks to shared/{python,typescript}/tools.
Update Dockerfiles, entrypoints, and configs to support the new
symlink-based tool resolution. Add PARITY_NOTES for frameworks
that have known gaps.
The showcase framework directories better reflect their role as
integration examples rather than distributable packages.
Renames showcase/packages/ -> showcase/integrations/ and updates
the test docker-compose file reference accordingly.