diff --git a/agents/hermes/Dockerfile b/agents/hermes/Dockerfile index a2c1162c1bd..b7b965b8fa0 100644 --- a/agents/hermes/Dockerfile +++ b/agents/hermes/Dockerfile @@ -139,7 +139,7 @@ RUN set -eu; \ exit 1; \ fi; \ fi; \ - rm -rf /root/.npm /root/.cache/electron /root/.cache/node-gyp; \ + rm -rf /root/.npm /root/.cache/electron /root/.cache/node-gyp /root/.cache/uv; \ test -d "$hermes_web_dist" ENV HERMES_WEB_DIST="/opt/hermes/hermes_cli/web_dist" @@ -1191,6 +1191,7 @@ RUN check_metadata() { \ && check_absent /root/.npm \ && check_absent /root/.cache/electron \ && check_absent /root/.cache/node-gyp \ + && check_absent /root/.cache/uv \ && check_absent /sandbox/.cache \ && check_metadata /scripts/patch-bundled-npm-brace-expansion.mts 'root:root 444' \ && check_metadata /scripts/patch-bundled-npm-tar.mts 'root:root 444' \ diff --git a/agents/hermes/Dockerfile.base b/agents/hermes/Dockerfile.base index ef5da59a5ed..023c808ace0 100644 --- a/agents/hermes/Dockerfile.base +++ b/agents/hermes/Dockerfile.base @@ -523,6 +523,8 @@ RUN set -eu; \ # differential. Keep this hash-verified downstream override at the first stable # release that fixes those issues plus GHSA-v9pg-7xvm-68hf. Re-review the # version and both hashes on every Hermes version bump. +# uv creates a phony .git cache marker even with --no-cache. Remove the cache +# after the final uv command because the root cache is not used at runtime. # hadolint ignore=DL3059 RUN printf '%s\n' \ "python-multipart==0.0.32 \\" \ @@ -533,7 +535,8 @@ RUN printf '%s\n' \ --no-deps --no-cache --require-hashes -r /tmp/multipart-req.txt \ && rm -f /tmp/multipart-req.txt \ && /opt/hermes/.venv/bin/python -c \ - "import multipart; assert multipart.__version__ == '0.0.32', multipart.__version__" + "import multipart; assert multipart.__version__ == '0.0.32', multipart.__version__" \ + && rm -rf /root/.cache/uv ENV PATH="/usr/local/bin:/opt/hermes/.venv/bin:${PATH}" \ HERMES_TUI_DIR="/opt/hermes/ui-tui" \ @@ -561,7 +564,7 @@ RUN chmod -R a+rX /opt/hermes/.venv \ # Gate the exact completed base filesystem before it can be published. COPY scripts/checks/node-tar-image-scan.mts /scripts/checks/node-tar-image-scan.mts RUN set -eu; \ - for build_only_path in /opt/hermes/tests /root/.npm /root/.cache/electron /root/.cache/node-gyp; do \ + for build_only_path in /opt/hermes/tests /root/.npm /root/.cache/electron /root/.cache/node-gyp /root/.cache/uv; do \ if [ -e "$build_only_path" ] || [ -L "$build_only_path" ]; then \ echo "ERROR: build-only Hermes path leaked into the base image: $build_only_path" >&2; \ exit 1; \ diff --git a/test/e2e/live/hermes-root-entrypoint-smoke.test.ts b/test/e2e/live/hermes-root-entrypoint-smoke.test.ts index 60f4270a350..5aa62770fb1 100644 --- a/test/e2e/live/hermes-root-entrypoint-smoke.test.ts +++ b/test/e2e/live/hermes-root-entrypoint-smoke.test.ts @@ -243,7 +243,7 @@ async function assertBuildOnlyPathsAbsent(probe: DockerProbe, container: string) probe, container, "build-only Hermes paths are present in the runtime image", - 'for path in /opt/hermes/tests /root/.npm /root/.cache/electron /root/.cache/node-gyp; do test ! -e "$path" && test ! -L "$path"; done', + 'for path in /opt/hermes/tests /root/.npm /root/.cache/electron /root/.cache/node-gyp /root/.cache/uv; do test ! -e "$path" && test ! -L "$path"; done', ); } diff --git a/test/hermes-dashboard-provisioning.test.ts b/test/hermes-dashboard-provisioning.test.ts index 4a7ca85ec3b..20356394c0c 100644 --- a/test/hermes-dashboard-provisioning.test.ts +++ b/test/hermes-dashboard-provisioning.test.ts @@ -49,7 +49,8 @@ function dashboardBuildCommand(hermesRoot: string, rootCache: string): string { .replaceAll("/opt/hermes", hermesRoot) .replaceAll("/root/.npm", path.join(rootCache, "npm")) .replaceAll("/root/.cache/electron", path.join(rootCache, "electron")) - .replaceAll("/root/.cache/node-gyp", path.join(rootCache, "node-gyp")); + .replaceAll("/root/.cache/node-gyp", path.join(rootCache, "node-gyp")) + .replaceAll("/root/.cache/uv", path.join(rootCache, "uv")); } describe("Hermes dashboard provisioning", () => { @@ -62,7 +63,7 @@ describe("Hermes dashboard provisioning", () => { fs.mkdirSync(hermesWebDir, { recursive: true }); fs.writeFileSync(path.join(hermesRoot, "package-lock.json"), '{"packages":{"web":{}}}\n'); fs.writeFileSync(path.join(hermesWebDir, "package.json"), "{}\n"); - for (const cache of ["npm", "electron", "node-gyp"]) { + for (const cache of ["npm", "electron", "node-gyp", "uv"]) { const cachePath = path.join(rootCache, cache); fs.mkdirSync(cachePath, { recursive: true }); fs.writeFileSync(path.join(cachePath, "build-only-cache"), "unused after image assembly\n"); @@ -82,7 +83,7 @@ describe("Hermes dashboard provisioning", () => { expect(calls).toContain(`npm run build --prefix ${hermesRoot} --workspace web`); expect(calls).toContain(`npm ci --omit=dev --workspaces=false --prefix ${hermesRoot}`); expect(fs.existsSync(hermesWebDist)).toBe(true); - for (const cache of ["npm", "electron", "node-gyp"]) { + for (const cache of ["npm", "electron", "node-gyp", "uv"]) { expect(() => fs.lstatSync(path.join(rootCache, cache))).toThrow(); } } finally { diff --git a/test/sandbox-provisioning.test.ts b/test/sandbox-provisioning.test.ts index 1a0ba6d48a7..1b1769b0b93 100644 --- a/test/sandbox-provisioning.test.ts +++ b/test/sandbox-provisioning.test.ts @@ -1325,7 +1325,7 @@ describe("Hermes sandbox provisioning", () => { fs.writeFileSync(path.join(hermesWebDir, "package.json"), "{}\n"); fs.writeFileSync(path.join(hermesWebDir, "package-lock.json"), "{}\n"); fs.mkdirSync(path.join(hermesWebDir, "node_modules"), { recursive: true }); - for (const cache of ["npm", "electron", "node-gyp"]) { + for (const cache of ["npm", "electron", "node-gyp", "uv"]) { const cachePath = path.join(rootCache, cache); fs.mkdirSync(cachePath, { recursive: true }); fs.writeFileSync(path.join(cachePath, "build-only-cache"), "unused after image assembly\n"); @@ -1338,7 +1338,8 @@ describe("Hermes sandbox provisioning", () => { .replaceAll("/opt/hermes", hermesRoot) .replaceAll("/root/.npm", path.join(rootCache, "npm")) .replaceAll("/root/.cache/electron", path.join(rootCache, "electron")) - .replaceAll("/root/.cache/node-gyp", path.join(rootCache, "node-gyp")); + .replaceAll("/root/.cache/node-gyp", path.join(rootCache, "node-gyp")) + .replaceAll("/root/.cache/uv", path.join(rootCache, "uv")); try { const { result, calls } = runLoggedDockerShell(command, tmp, [ 'npm() { printf "npm %s\\n" "$*" >> "$call_log"; if [ -n "${hermes_web_dist:-}" ] && [ "${1:-}" = "run" ] && [ "${2:-}" = "build" ]; then mkdir -p "$hermes_web_dist"; fi; }', @@ -1349,7 +1350,7 @@ describe("Hermes sandbox provisioning", () => { expect(calls).toContain(`npm run build --prefix ${hermesWebDir}`); expect(fs.existsSync(hermesWebDist)).toBe(true); expect(fs.existsSync(path.join(hermesWebDir, "node_modules"))).toBe(false); - for (const cache of ["npm", "electron", "node-gyp"]) { + for (const cache of ["npm", "electron", "node-gyp", "uv"]) { expect(() => fs.lstatSync(path.join(rootCache, cache))).toThrow(); } } finally {