Fix Playwright setup destroying its own install, and the migration notice that stayed silent #25

Merged
jknapp merged 2 commits from fix/playwright-container-setup into main 2026-08-14 04:34:20 +00:00
Owner

Setting up the browser view failed on every container tried, and re-running it reproduced the same broken state each time. Three separate faults were stacked on top of each other.

The setup deleted the package it had just installed

install_packages ran two npm install --no-save commands into /workspace, which has no package.json. Without a manifest, npm treats the command line as the entire statement of what the tree should contain and prunes the rest, so the second install removed the first package:

$ npm install --no-save playwright@1.63.0-alpha-2026-08-05
removed 3 packages, and changed 1 package in 2s
$ ls node_modules/@playwright/     # empty

That empty @playwright/ directory is exactly the state the pane then reported as "not installed". The second install now names both specs.

Nothing ever configured the browser

playwright-cli fell back to channel chrome — system Google Chrome — with the Chromium sandbox on. These containers forbid unprivileged user namespaces, so it aborted with Failed to move to new namespace ... Operation not permitted. On a base image without Google Chrome the same default failed the other way, as Chromium distribution 'chrome' is not found. One cause, two error messages, neither looking like configuration.

entrypoint.sh now seeds ~/.playwright/cli.config.json on every start. It has to be seeded rather than baked: ~/.playwright is inside the home volume, so an image copy would reach new projects only and every existing project would stay broken. The seeding path does no npm, no download, no apt and no launch — measured at ~2 ms.

The launch check verified a configuration the viewer never uses

It launched bundled chromium with no channel, which resolves to chromium-headless-shell, while the viewer's config pins chrome-for-testing — the full chromium build, a separate download. A container could pass every check and still fail in the pane with Browser "chrome-for-testing" is not installed. That is not hypothetical: one project had a stale chromium-1217 while its Playwright wanted chromium-1237, and every check called it healthy. Chromium is now verified on both channels and a failure names the channel.

Repairing containers that are already broken

container/triple-c-playwright-heal is idempotent and verifies by actually launching a browser rather than trusting the preceding steps — which is how the stale-revision case surfaced. It also installs socat when missing, which makes the pane report 127.0.0.1 sent an invalid response while the container side is entirely healthy. It lives in /usr/local/bin so a later fix to it can still reach an existing project.

The migration notice that could not fire

Separately, a project can be out of date and say nothing, in two compounding ways.

create_container always writes triple-c.base-image-id, even when unknown — deliberately, so an inherited label cannot ride a snapshot forever. That makes Some("") the ordinary reading for a container with no established lineage. The lookup filtered for emptiness only on the final result, so the empty string satisfied the container branch and skipped the snapshot, meaning a snapshot that had recorded a lineage was never consulted. Each source is now filtered before it can answer.

A genuinely pre-label project stays unknown, and should — its ancestor is not knowable, and inventing one would make it look permanently current. The probe is the intended signal for those. But when the probe failed, the command returned early with nothing populated, the banner found no gaps and rendered null, and the probe_error it already knew how to display sat behind a gate that returned before reaching it. Silence there reads as "up to date", and it is likeliest for the oldest and largest projects — one real project's manifest measured 6.93 MB against an 8 MB inspection cap.

Testing

  • 277 Rust tests, 354 frontend tests, npm run build clean.
  • pick_recorded_lineage is a plain function with a test verified to fail against the old logic (left: None, right: Some("sha256:base")).
  • The launch probe was extracted and executed for real, not just compiled — passing on both channels, and naming the channel on failure.
  • The heal script was run against three live containers, including one deliberately broken by reproducing the npm prune.

Not addressed

The banner still renders only in the Overview tab of Project Home, and nothing polls it. Surfacing staleness app-wide is a UI decision rather than a bug fix.

🤖 Generated with Claude Code

Setting up the browser view failed on every container tried, and re-running it reproduced the same broken state each time. Three separate faults were stacked on top of each other. ### The setup deleted the package it had just installed `install_packages` ran two `npm install --no-save` commands into `/workspace`, which has no `package.json`. Without a manifest, npm treats the command line as the entire statement of what the tree should contain and prunes the rest, so the second install removed the first package: ``` $ npm install --no-save playwright@1.63.0-alpha-2026-08-05 removed 3 packages, and changed 1 package in 2s $ ls node_modules/@playwright/ # empty ``` That empty `@playwright/` directory is exactly the state the pane then reported as "not installed". The second install now names both specs. ### Nothing ever configured the browser `playwright-cli` fell back to channel `chrome` — system Google Chrome — with the Chromium sandbox on. These containers forbid unprivileged user namespaces, so it aborted with `Failed to move to new namespace ... Operation not permitted`. On a base image without Google Chrome the same default failed the other way, as `Chromium distribution 'chrome' is not found`. One cause, two error messages, neither looking like configuration. `entrypoint.sh` now seeds `~/.playwright/cli.config.json` on every start. It has to be seeded rather than baked: `~/.playwright` is inside the home volume, so an image copy would reach new projects only and every existing project would stay broken. The seeding path does no npm, no download, no apt and no launch — measured at ~2 ms. ### The launch check verified a configuration the viewer never uses It launched bundled chromium with no channel, which resolves to `chromium-headless-shell`, while the viewer's config pins `chrome-for-testing` — the full chromium build, a separate download. A container could pass every check and still fail in the pane with `Browser "chrome-for-testing" is not installed`. That is not hypothetical: one project had a stale `chromium-1217` while its Playwright wanted `chromium-1237`, and every check called it healthy. Chromium is now verified on both channels and a failure names the channel. ### Repairing containers that are already broken `container/triple-c-playwright-heal` is idempotent and verifies by actually launching a browser rather than trusting the preceding steps — which is how the stale-revision case surfaced. It also installs `socat` when missing, which makes the pane report `127.0.0.1 sent an invalid response` while the container side is entirely healthy. It lives in `/usr/local/bin` so a later fix to it can still reach an existing project. ### The migration notice that could not fire Separately, a project can be out of date and say nothing, in two compounding ways. `create_container` always writes `triple-c.base-image-id`, even when unknown — deliberately, so an inherited label cannot ride a snapshot forever. That makes `Some("")` the ordinary reading for a container with no established lineage. The lookup filtered for emptiness only on the final result, so the empty string satisfied the container branch and skipped the snapshot, meaning a snapshot that *had* recorded a lineage was never consulted. Each source is now filtered before it can answer. A genuinely pre-label project stays unknown, and should — its ancestor is not knowable, and inventing one would make it look permanently current. The probe is the intended signal for those. But when the probe failed, the command returned early with nothing populated, the banner found no gaps and rendered `null`, and the `probe_error` it already knew how to display sat behind a gate that returned before reaching it. Silence there reads as "up to date", and it is likeliest for the oldest and largest projects — one real project's manifest measured 6.93 MB against an 8 MB inspection cap. ### Testing - 277 Rust tests, 354 frontend tests, `npm run build` clean. - `pick_recorded_lineage` is a plain function with a test verified to fail against the old logic (`left: None, right: Some("sha256:base")`). - The launch probe was extracted and executed for real, not just compiled — passing on both channels, and naming the channel on failure. - The heal script was run against three live containers, including one deliberately broken by reproducing the npm prune. ### Not addressed The banner still renders only in the Overview tab of Project Home, and nothing polls it. Surfacing staleness app-wide is a UI decision rather than a bug fix. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
jknapp added 2 commits 2026-08-14 04:25:33 +00:00
Setting up the browser view failed on every container, and re-running it
reproduced the same broken state, because the setup destroyed its own work.

`install_packages` ran two `npm install --no-save` commands into
/workspace, which has no package.json. With no manifest, npm treats the
command line as the whole statement of what the tree should contain and
prunes the rest, so installing `playwright` second removed the
`@playwright/cli` installed first: "removed 3 packages", leaving an empty
node_modules/@playwright/ behind playwright and playwright-core. That
empty directory is exactly what the pane then reported as missing. The
second install now names both specs; the first one is already present, so
it costs nothing and is only there to stop npm pruning it.

Two failures were waiting behind that one:

Nothing in the tree ever configured the browser, so playwright-cli fell
back to channel `chrome` — system Google Chrome — with the Chromium
sandbox on. These containers forbid unprivileged user namespaces, so it
aborted with "Failed to move to new namespace ... Operation not
permitted"; on a base image without Google Chrome the same default failed
as "Chromium distribution 'chrome' is not found". entrypoint.sh now seeds
~/.playwright/cli.config.json on every start, which is the only way to
reach existing projects: ~/.playwright is inside the home volume, so an
image copy would reach new projects only.

The launch check passed for a configuration the viewer never uses. It
launched bundled chromium with no channel, which resolves to
chromium-headless-shell, while the viewer's config pins
chrome-for-testing — the full chromium build, a separate download. A
container could pass every check and still fail in the pane with 'Browser
"chrome-for-testing" is not installed', which is what a stale
chromium-1217 against a wanted chromium-1237 did. Chromium is now
verified on both channels, the sandbox setting is stated rather than
inherited from a default, and a failure names the channel.

triple-c-playwright-heal repairs all of it on a container that is already
broken, including the missing socat that makes the pane report
"127.0.0.1 sent an invalid response" while the container side is
perfectly healthy. It verifies by launching a browser rather than
trusting the preceding steps — which is how the stale-revision case was
found — and lives in /usr/local/bin so a fix to it can still reach an
existing project.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Stop an empty base-image label from silencing the migration notice
Build App (Preview) / compute-version (pull_request) Successful in 3s
Build Container / build-container (pull_request) Successful in 1m5s
Build App (Preview) / create-release (pull_request) Successful in 2s
Build App (Preview) / build-macos (pull_request) Successful in 2m41s
Build App (Preview) / build-linux (pull_request) Successful in 5m31s
Build App (Preview) / build-windows (pull_request) Successful in 6m24s
Build App (Preview) / prune-previews (pull_request) Successful in 8s
84a67fcd0d
A project can be out of date and say nothing about it, in two ways that
compound: the lineage lookup treats "unknown" as an answer, and the
fallback that exists for unknown lineage disappears when its probe fails.

`create_container` always writes triple-c.base-image-id, even when the
value is unknown — deliberately, so an inherited image label cannot ride
a snapshot forever. That makes Some("") the ordinary reading from a
container whose lineage was never established. The lookup filtered for
emptiness only on the final result, so that empty string satisfied the
container branch and skipped the snapshot entirely: a snapshot that had
recorded a real lineage was never consulted, and the project reported
"unknown" with the answer one lookup away. Each source is now filtered
before it can answer, in pick_recorded_lineage, which is a plain function
so the case has a test that fails against the old logic.

A genuinely pre-label project stays unknown, and should: its ancestor is
not knowable, and inventing one would make it look permanently current.
The probe is the intended signal for those — but if the probe failed,
get_container_staleness returned early with nothing populated, the banner
found no gaps and rendered null, and the probe_error it already knew how
to display sat behind a gate that returned before reaching it. Silence
there is indistinguishable from "up to date", and it is likeliest for the
oldest and largest projects, whose manifests are the ones apt to exceed
the inspection limit — one real project measured 6.93 MB against an 8 MB
cap. An unknown-lineage container whose probe failed now says the check
could not be completed, with the reason, under the tone that means
unresolved rather than the one that means something is wrong.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jknapp merged commit 0e6566d903 into main 2026-08-14 04:34:20 +00:00
jknapp deleted branch fix/playwright-container-setup 2026-08-14 04:35:43 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: CyberCoveLLC/Triple-C#25