2 Commits
Author SHA1 Message Date
tmu 048a8870e6 🐛 Write the tool-cache completion marker into the image
CI / release-gate (push) Successful in 3s
CI / build (push) Successful in 39s
CI / publish (push) Skipped
CI / maintain (push) Failing after 15s
setup-node still downloaded although the image carried a perfect
/opt/hostedtoolcache/node/26.8.2/x64/ tree. actions/tool-cache accepts a
cached tool only when the sibling marker <version>/<arch>.complete exists
(tc.find() tests it explicitly); a bare directory is ignored, so the probe
fell through to the download. The marker is what tc.cacheDir() writes after
installing a tool, so the baked entry must create it too.

Job-container diagnostics also confirmed the path was never in question:
RUNNER_TOOL_CACHE=/opt/hostedtoolcache, no mount over it, node -v from the
baked path prints v26.8.2.

CONTRIBUTING records both invariants — the marker, and the fact that a
Dockerfile change keeps the same tag, which forcePull=false can hide.
2026-09-14 15:59:23 +00:00
tmu f0b28c81c0 🔊 Log tool-cache state in the CI build job
CI / release-gate (push) Successful in 2s
CI / build (push) Successful in 40s
CI / publish (push) Skipped
CI / maintain (push) Failing after 42s
The custom image provably carries /opt/hostedtoolcache/node/26.8.2/x64/bin/node
(ls inside the image confirms the layout, modes and the 150 MB binary), yet
setup-node still downloads. So the miss is runtime-only: either the runner is
not running that image or something shadows the path inside the job container.
This step prints mounts, the tool-cache listing, a direct node -v from the
baked path and the RUNNER_* env, then setup-node runs unchanged.

Temporary: remove once the cause is known.
2026-09-14 15:55:52 +00:00
3 changed files with 30 additions and 0 deletions

No files matched your search

+18
View File
@@ -63,6 +63,24 @@ jobs:
- /data/gitea-pages:/data/gitea-pages - /data/gitea-pages:/data/gitea-pages
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
# TEMPORARY (remove once understood): the image provably carries
# the tool-cache entry (verified with `docker run --rm <image> ls
# /opt/hostedtoolcache/node/26.8.2/x64/bin`), yet setup-node still
# downloads. Print what the job container actually sees at
# runtime, mounts first — a mount over /opt/hostedtoolcache would
# hide the baked directory from the probe.
- name: Tool cache diagnostics
run: |
id
grep -E 'hostedtoolcache|workspace|overlay' /proc/mounts || true
ls -la /opt/hostedtoolcache || true
ls -la /opt/hostedtoolcache/node || true
ls -la "/opt/hostedtoolcache/node/$(tr -d '[:space:]' < .node-version)/" || true
test -f "/opt/hostedtoolcache/node/$(tr -d '[:space:]' < .node-version)/x64.complete" && echo MARKER-PRESENT || echo MARKER-MISSING
ls -la "/opt/hostedtoolcache/node/$(tr -d '[:space:]' < .node-version)/x64/bin" || true
"/opt/hostedtoolcache/node/$(tr -d '[:space:]' < .node-version)/x64/bin/node" -v || true
env | grep -iE 'RUNNER|TOOL|CACHE' || true
head -2 /etc/os-release
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
node-version-file: .node-version node-version-file: .node-version
+5
View File
@@ -102,6 +102,11 @@ Bumping Node is one coordinated change, committed as a unit:
Skipping step 2 fails CI at image pull; skipping step 3 silently reverts to the per-job download. Skipping step 2 fails CI at image pull; skipping step 3 silently reverts to the per-job download.
Two invariants the image must satisfy for the probe to hit, both easy to break:
- **The `x64.complete` marker.** `actions/tool-cache` accepts a cached tool only when `<version>/<arch>.complete` exists next to the directory (`tc.find()` checks it); a plausible-looking `node/<version>/x64/` alone is ignored and the download happens anyway. See the comment in [docker/Dockerfile](./docker/Dockerfile).
- **Tag freshness.** The tag encodes only the Node version, so a Dockerfile change (like the marker above) produces _new content under an unchanged tag_. `act_runner` skips the pull when a tag of that name already exists locally (`forcePull=false` in the job log), so the runner must either force-pull (`force_pull` under `container:` in its `config.yaml`, if the installed version has it) or have the tag removed on the runner host (`docker rmi gitea.e1nsnull.de/tmu/act-ci:<version>`) after any image change. Symptom of getting this wrong: CI keeps running the previous image while the registry shows the new digest.
## Publishing workflow ## Publishing workflow
Publishing is CI-only by policy. Local `npm publish` is not supported. The maintainer triggers releases from `main`: Publishing is CI-only by policy. Local `npm publish` is not supported. The maintainer triggers releases from `main`:
+7
View File
@@ -39,6 +39,13 @@ ARG NODE_VERSION=26.8.2
# node image: bin/ + lib/ under /usr/local → tool cache: bin/ + lib/ under <ver>/x64. # node image: bin/ + lib/ under /usr/local → tool cache: bin/ + lib/ under <ver>/x64.
COPY --from=nodebase /usr/local /opt/hostedtoolcache/node/${NODE_VERSION}/x64 COPY --from=nodebase /usr/local /opt/hostedtoolcache/node/${NODE_VERSION}/x64
# actions/tool-cache only accepts a cached tool when the sibling marker file
# "<version>/<arch>.complete" exists — tc.find() checks it and falls back to
# downloading otherwise, however complete the directory is. The marker is what
# tc.cacheDir() writes after *it* installs a tool, so a pre-baked entry has to
# reproduce it explicitly.
RUN touch "/opt/hostedtoolcache/node/${NODE_VERSION}/x64.complete"
# Fail the build (not CI) if the overlay or the version arg were wrong. # Fail the build (not CI) if the overlay or the version arg were wrong.
# Shell form on purpose: exec form (`RUN [...]`) does not expand ARG values. # Shell form on purpose: exec form (`RUN [...]`) does not expand ARG values.
RUN "/opt/hostedtoolcache/node/${NODE_VERSION}/x64/bin/node" --version RUN "/opt/hostedtoolcache/node/${NODE_VERSION}/x64/bin/node" --version