perf(runner): the image carries the actions build.yml names, cloned

A forge runner lives one job and began each with an empty act cache, and
act clones an action with go-git, full history, before the first step:
forge run 16634 spent 156 of each job's 161 s of setup cloning
setup-node (72 s), setup-go (42 s), setup-bun (20 s) and checkout
(17 s), the three it then skipped included.

The image now clones each into ~/.cache/act/<sha256 of the uses string>
with every ref the remote advertises, so the runner opens it and fetches
what moved since the build. Timed with act's own clone executor: 0.2 to
1.1 s each, against 2 to 90 s cold; with the branches and tags alone it
still fetched every pull request's objects, 10 to 40 s each. It adds
some 340 MB to the image.

hanzo.yml's runner-preseeds fails when build.yml names an action the
image does not carry.
This commit is contained in:
2026-10-20 08:05:25 -07:00
parent 6de3b93fcf
commit e1c4e4876b
2 changed files with 36 additions and 0 deletions
+14
View File
@@ -117,6 +117,20 @@ test:
echo "::error::the test gate is guarded by \`inputs.mode != 'delegate'\` — delegate would skip it"; exit 1
fi
echo "OK: test gate at line $t, delegate POST at line $d, gate not delegate-guarded"
- name: runner-preseeds
# The runner image clones every action build.yml's steps name, so a job
# does not spend its first two and a half minutes doing it (runner/Dockerfile,
# ACTIONS). An action added to build.yml and not to that list costs every
# job its clone again with nothing failing, so this says so.
run: |
set -e
want=$(grep -E '^ +(- )?uses: [A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+@' .github/workflows/build.yml \
| sed -E 's/^ +(- )?uses: //; s/ .*//' | sort -u)
have=$(sed -n 's/^ARG ACTIONS="\(.*\)"$/\1/p' runner/Dockerfile | tr ' ' '\n' | sort -u)
[ -n "$have" ] || { echo "::error::runner/Dockerfile has no ARG ACTIONS; a rename broke this assertion, fix the assertion"; exit 1; }
miss=$(comm -23 <(printf '%s\n' "$want") <(printf '%s\n' "$have"))
[ -z "$miss" ] || { echo "::error::build.yml uses actions runner/Dockerfile does not preseed: $miss"; exit 1; }
echo "OK: the runner image preseeds all $(printf '%s\n' "$want" | grep -c .) actions build.yml's steps name"
- name: publish-waits
# A push during a publish must wait for it, not cancel it. Cancelling is
# one expression away and reads as harmless in a diff, and on a branch
+22
View File
@@ -200,4 +200,26 @@ RUN useradd -m -u 1001 -s /bin/bash runner \
USER runner
WORKDIR /data
RUN chown -R runner:runner /opt/actions-runner
# The actions build.yml declares, already cloned where the act runner looks for
# them: ~/.cache/act/<sha256 of the `uses:` string>, every ref the remote
# advertises included. A runner lives one job, so it began each one with an
# empty cache, and its clone is go-git's, full history and pure Go: forge run
# 16634 spent 156 s of each job's 161 s before the first step cloning these
# five (setup-node 72 s, setup-go 42 s, setup-bun 20 s, checkout 17 s), the
# three it would skip included, because a job prepares every action it names.
# With the clone here the runner opens it and fetches what moved since the
# image was built. Measured with act's own clone executor: 0.2 to 1.1 s each,
# against 2 to 90 s cold. A clone of the branches and tags alone is not
# enough: act fetches refs/*, so it then downloads every pull request's
# objects, 10 to 40 s each. hanzo.yml's `runner-preseeds` holds this list to
# build.yml's steps.
ARG ACTIONS="actions/checkout@v4 actions/setup-go@v5 actions/setup-node@v4 oven-sh/setup-bun@v2 dtolnay/rust-toolchain@stable"
RUN set -eux; \
for a in $ACTIONS; do \
d="/home/runner/.cache/act/$(printf %s "$a" | sha256sum | cut -c1-64)"; \
git clone -q "https://github.com/${a%@*}" "$d"; \
git -C "$d" fetch -q --update-head-ok origin '+refs/*:refs/*' '+HEAD:refs/heads/HEAD'; \
git -C "$d" rev-parse -q --verify "${a#*@}^{commit}" >/dev/null; \
done
ENTRYPOINT ["/usr/bin/tini","--","/usr/local/bin/runner"]