Repository navigation
perf(sandbox): stop re-shipping the runtime image on every release - #4538
Merged
Merged
Conversation
This was referenced Oct 8, 2026
yannickmonney
force-pushed
the
perf/sandbox-image-layers
branch
from
October 8, 2026 12:47
c32fc54 to
4238f8c
Compare
This was referenced Oct 8, 2026
The amd64 release build also imported the GHA cache main CI warms. That cache is evicted often (the repository holds ~10.7 GB against GitHub's 10 GB limit), and a main build that rebuilt from an evicted cache exported new bytes for unchanged layers; the next release then published them. v0.5.76's amd64 runtime image changed 1.98 of its 2.03 GB compressed with no image input changed, while arm64, built from the same source with the registry cache alone, changed none. Every host downloaded ~2 GB and stored ~6 GB more on such an upgrade. Releases now read only their own per-architecture registry cache, so a reused layer keeps the previous release's bytes. Pull request and main builds read that cache as well, after their GHA cache, so a build whose GHA cache was evicted reuses the released layers instead of rebuilding all of them; they still never write it.
Every harness, browser and vision stage was built FROM tooling-base, the one chain that also held the OS packages, Office, TeX, the document libraries, the Docker engine and the Claude Code environment defaults, and a guard required it. Any edit there (two apt packages for #4395, the sharp security bump in #4450, an ENV tweak) re-ran every harness install and could re-ship ~2 GB of layers to every host. - harness-base (Python slim + Node + download tools) now builds the harnesses, the browser and the document Node libraries; the document Python libraries install into their own prefix, Hermes builds on them, and all of these arrive in the runtime stage as `COPY --link` layers. - The OS chain runs from the largest, stablest apt set to the most often extended (fonts and browser libraries, Office, TeX, the Docker engine, then the everyday tools), and its environment variables moved to the runtime stage, below every install. - The guard now requires that no stage but the runtime reaches the OS chain, that the OS chain holds no ENV and keeps its apt order, and that the document libraries ship as layers of their own. Every stage, and the full runtime image including the Playwright MCP manifest recording, was built locally (arm64).
- Remove Cursor's two macOS-only single-executable builds (~265 MB): its computer-use re-exec downloads and codesign-checks its own copy under ~/.cursor, and `cursor-agent` runs the bundled node. - Remove Codex's realtime voice host (~77 MB with its media libraries); the sandbox drives `codex exec`, which starts without it. - Keep only this architecture's Linux ripgrep among the five Qwen vendors. - Compile the Python stdlib to hash-based bytecode. The python image deletes every stdlib .pyc and sessions run with a read-only root and PYTHONDONTWRITEBYTECODE, so each wrapper, launcher, Hermes turn, run_code script and document skill compiled its stdlib imports again (~0.1 s per wrapper start, up to ~1 s for a broad import set). - Carry the node image's yarn and repoint its nodejs link: yarn, yarnpkg and nodejs were dangling links first on PATH. - Pin python, node, debian and uv by digest, like oven/bun already is, so a base refresh is a Renovate change instead of whatever the tag held when a cache missed. The image suite now asserts no dangling link on PATH, working yarn and nodejs, and baked stdlib bytecode. Probed locally on the built image: cursor, qwen, codex and hermes start, `codex exec` reaches its network phase, and `pip check` is clean.
yannickmonney
force-pushed
the
perf/sandbox-image-layers
branch
from
October 9, 2026 02:54
6f07b7e to
4b161f2
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Every host downloads and stores each runtime-image layer whose bytes change. Measured from the registry manifests, almost every release re-shipped the whole ~2 GB (compressed) image; new compressed bytes a host pulled per upgrade, amd64: 0.5.70→71 1.88 GB, 71→72 2.17 GB, 72→73 2.17 GB, 73→74 2.03 GB, 74→75 1.25 GB, 75→76 1.98 GB. Each of those also added ~6 GB on disk (old versions are never removed; #4533 fixes that half).
Two causes:
FROM tooling-base(a guard required it), the one chain that also held OS packages, Office, TeX, the document libraries, the Docker engine and theCLAUDE_CODE_*env defaults. Adding two apt packages (fix(platform): synchronize external task lifecycles #4395) or asharpsecurity bump (fix(deps): update dependency sharp to v0.35.5 [security] #4450) re-ran every harness install and re-shipped unrelated layers.Change
ci:commit): releases read only their own per-arch registry cache. PR and main builds read it too (after GHA, never writing it), so a build whose GHA cache was evicted reuses released layers instead of a cold ~14 min rebuild.perf:commit 2): a smallharness-base(Python slim + Node + download tools) builds the harnesses, the browser and the document Node libraries; the document Python libraries install into their own prefix and Hermes builds on them; everything arrives asCOPY --linklayers. The OS chain runs from the largest, stablest apt set to the most often extended, and holds noENV. The guard now encodes this (no stage butruntimereaches the OS chain; noENVthere; apt order; document libraries as their own layers).perf:commit 3): drop Cursor's two macOS-only SEA builds (~265 MB unpacked), Codex's realtime voice host (~77 MB), and Qwen's ripgrep for other platforms; bake the Python stdlib's bytecode (the base image strips it and the read-only root can't cache it, so every Python process recompiled its stdlib imports: ~0.1 s per wrapper start, up to ~1 s for broad imports); carry the node image's yarn and fix the danglingyarn/yarnpkg/nodejslinks on PATH; pinpython,node,debiananduvby digest (likeoven/bunalready is) so base refreshes come through Renovate.Expected effect
Proof
runtimetarget built locally (arm64, throwaway builder), including the step that launches the real Playwright MCP server to record its manifests. Probes on the built image: no dangling links;yarn 1.22.22,nodejs,bunxrun; stdlib.pycpresent;cursor,qwen,codex,hermesstart;codex execreaches its network phase without the voice host;pip checkclean; Hermes installed only its own additions on top of the shared document libraries.dockerfile-fail-closed.guard.test.ts61/61; the new rules fail 17 cases against the old Dockerfile.build-ci,ci-build-optimization,ci-image-pulls,deployment-cipass.The first release after this merges re-ships the image once (new layout); later releases reuse it.