Add tea, gh and ripgrep to the runner image #8

Merged
geusebio merged 1 commit from feature/tea-gh-ripgrep into main 2026-08-09 17:41:00 +00:00
Owner

Adds the three CLIs that CI here keeps having to work around, and pays for them
by cleaning up build downloads the image was already shipping.

What was actually missing

Inventoried the published image before changing anything:

$ docker run --rm ghcr.io/matthewbaggett/act-runner:php8.5 bash -lc '...'
tea        MISSING
gh         MISSING
rg         MISSING
jq         /usr/bin/jq
yq         /usr/bin/yq
curl git go node php composer docker   all present

jq and yq are already in the image via EXTRA_PACKAGES, so they are left
alone — the suspicion that they were absent was wrong. That leaves tea, gh
and rg.

Tool Version Why
tea 0.15.0 The important one. Every forge call in CI today is hand-rolled curl against /api/v1 with a manual Authorization: token header. Past the 0.14.2 floor for --draft / pulls edit --ready.
gh 2.97.0 Requested — see the caveat below.
rg 15.2.0 Used throughout our skills; was absent.

All three are single static binaries fetched by pinned ARG, verified against
the upstream checksums file, on both amd64 and arm64.

The gh tension, stated plainly

Someones.Computer bans gh outright — Forgejo is canonical there, GitHub is
a read-only mirror, mirrored CI does not run, and gh reports stale or absent
state without saying so. GHAR is shared across the org and other repos genuinely
live on github.com, so the binary is here for them.

Installing it must not read as blessing it where it is banned, so the new
README.md says so in its own section, and the Dockerfile comment points at it.

Size: −29 MiB, not +72

The three binaries are 72.0 MiB installed (gh 39.1, tea 27.7, rg 5.2). While
measuring that I found the image was already shipping its own build downloads —
the OpenTofu and AWS blocks cd "$(mktemp -d)" and never clean up:

$ docker run --rm act-runner-base:php8.5 find /tmp -type f -size +100k
36M /tmp/tmp.VbKjioEcM2/eksctl_Linux_amd64.tar.gz
33M /tmp/tmp.r5JHqFEKEG/aws-iam-authenticator_0.7.18_linux_amd64
34M /tmp/tmp.s8PXJ3TtC4/tofu_1.12.5_linux_amd64.zip

That is ~101 MiB re-pulled on every job, since the runners use force_pull on a
shared serialised worker. All three blocks now trap 'rm -rf "$WORK"' EXIT, which
is in scope precisely because size is the binding constraint on this image:

baseline (main) this branch delta
Uncompressed 1542.8 MiB 1513.8 MiB −29.0 MiB
Compressed (pull cost) 576.2 MiB 523.7 MiB −52.5 MiB

Compressed falls further than uncompressed because what was removed was already-
compressed archives, while what was added is uncompressed ELF.

Both figures are a local docker build --build-arg PHP_VERSION=8.5 of main vs
this branch, same host, same base.

What was deliberately not touched

hadolint, markdownlint and actionlint stay out. They are run as pinned
docker images by consumers (bin/lint-*.sh in Someones.Computer) so CI and a
laptop execute byte-identical versions; baking them in would silently break that.
Same for dive and the Playwright image. README.md records the reasoning so the
next person does not "helpfully" add them.

yq was listed as optional-if-a-caller-exists — moot, it was already installed.

Verification

  • Acceptance command passes:
    $ docker run --rm act-runner-test:php8.5 bash -lc 'tea --version && gh --version && jq --version'
    Version: 0.15.0	golang: 1.26.5	go-sdk: v1.2.0
    gh version 2.97.0 (2026-07-31)
    jq-1.8.1
    
  • Each tool also runs once inside its own build layer, so a bad download fails
    the build rather than the job that needs it.
  • arm64 built under qemu. Worth calling out: PR builds are amd64-only and
    main publishes both, so a broken arm64 path would not fail until after merge.
    Built the install block for linux/arm64 separately — all three fetch,
    checksum-verify and run.
  • hadolint produces no new class of warning; the new block draws the same
    DL4006/DL3003/SC3040 trio the existing TOFU_INSTALL and AWS_TOOLS_INSTALL
    blocks already do (false positives — the file sets SHELL ["/bin/bash", "-c"]).
  • README.md is clean under Someones.Computer's .markdownlint.yaml.

One claim in an early draft was wrong and is corrected in the final comment:
ripgrep is packaged by Alpine on every base we build, including 3.15. The
actual reason for the upstream archive is version consistency — apk gives 13.0.0
on the php7.4 base against 14.1.1 on php8.5, so a workflow's rg would depend on
which PHP tag it ran under. Same argument the OpenTofu block already makes.

Follow-up, not in this PR

  • Someones.Computer's .github/workflows/cancel-merged.yml and
    .github/actions/dispatch-workflow/action.yml can drop their hand-rolled
    curl + php -r JSON filtering for tea api now. Separate repo, separate PR.
  • The Claude Code CLI (docs/claude-in-ci-plan.html, branch
    docs/claude-in-ci-proposal) also wants a pinned entry here with
    DISABLE_UPDATES=1 / DISABLE_INSTALLATION_CHECKS=1. Left out deliberately —
    that proposal is not approved yet, and this PR should not presuppose it.
Adds the three CLIs that CI here keeps having to work around, and pays for them by cleaning up build downloads the image was already shipping. ## What was actually missing Inventoried the published image before changing anything: ``` $ docker run --rm ghcr.io/matthewbaggett/act-runner:php8.5 bash -lc '...' tea MISSING gh MISSING rg MISSING jq /usr/bin/jq yq /usr/bin/yq curl git go node php composer docker all present ``` **`jq` and `yq` are already in the image** via `EXTRA_PACKAGES`, so they are left alone — the suspicion that they were absent was wrong. That leaves `tea`, `gh` and `rg`. | Tool | Version | Why | |---|---|---| | `tea` | 0.15.0 | The important one. Every forge call in CI today is hand-rolled `curl` against `/api/v1` with a manual `Authorization: token` header. Past the 0.14.2 floor for `--draft` / `pulls edit --ready`. | | `gh` | 2.97.0 | Requested — see the caveat below. | | `rg` | 15.2.0 | Used throughout our skills; was absent. | All three are single static binaries fetched by pinned `ARG`, verified against the upstream checksums file, on both amd64 and arm64. ## The `gh` tension, stated plainly `Someones.Computer` **bans `gh` outright** — Forgejo is canonical there, GitHub is a read-only mirror, mirrored CI does not run, and `gh` reports stale or absent state without saying so. GHAR is shared across the org and other repos genuinely live on github.com, so the binary is here for them. Installing it must not read as blessing it where it is banned, so the new `README.md` says so in its own section, and the Dockerfile comment points at it. ## Size: −29 MiB, not +72 The three binaries are 72.0 MiB installed (gh 39.1, tea 27.7, rg 5.2). While measuring that I found the image was already shipping its own build downloads — the OpenTofu and AWS blocks `cd "$(mktemp -d)"` and never clean up: ``` $ docker run --rm act-runner-base:php8.5 find /tmp -type f -size +100k 36M /tmp/tmp.VbKjioEcM2/eksctl_Linux_amd64.tar.gz 33M /tmp/tmp.r5JHqFEKEG/aws-iam-authenticator_0.7.18_linux_amd64 34M /tmp/tmp.s8PXJ3TtC4/tofu_1.12.5_linux_amd64.zip ``` That is ~101 MiB re-pulled on every job, since the runners use `force_pull` on a shared serialised worker. All three blocks now `trap 'rm -rf "$WORK"' EXIT`, which is in scope precisely because size is the binding constraint on this image: | | baseline (`main`) | this branch | delta | |---|---|---|---| | Uncompressed | 1542.8 MiB | 1513.8 MiB | **−29.0 MiB** | | Compressed (pull cost) | 576.2 MiB | 523.7 MiB | **−52.5 MiB** | Compressed falls further than uncompressed because what was removed was already- compressed archives, while what was added is uncompressed ELF. Both figures are a local `docker build --build-arg PHP_VERSION=8.5` of `main` vs this branch, same host, same base. ## What was deliberately not touched `hadolint`, `markdownlint` and `actionlint` stay out. They are run as pinned docker images by consumers (`bin/lint-*.sh` in `Someones.Computer`) so CI and a laptop execute byte-identical versions; baking them in would silently break that. Same for `dive` and the Playwright image. `README.md` records the reasoning so the next person does not "helpfully" add them. `yq` was listed as optional-if-a-caller-exists — moot, it was already installed. ## Verification - Acceptance command passes: ``` $ docker run --rm act-runner-test:php8.5 bash -lc 'tea --version && gh --version && jq --version' Version: 0.15.0 golang: 1.26.5 go-sdk: v1.2.0 gh version 2.97.0 (2026-07-31) jq-1.8.1 ``` - Each tool also runs once **inside** its own build layer, so a bad download fails the build rather than the job that needs it. - **arm64 built under qemu.** Worth calling out: PR builds are amd64-only and `main` publishes both, so a broken arm64 path would not fail until after merge. Built the install block for `linux/arm64` separately — all three fetch, checksum-verify and run. - `hadolint` produces no new class of warning; the new block draws the same DL4006/DL3003/SC3040 trio the existing `TOFU_INSTALL` and `AWS_TOOLS_INSTALL` blocks already do (false positives — the file sets `SHELL ["/bin/bash", "-c"]`). - `README.md` is clean under `Someones.Computer`'s `.markdownlint.yaml`. One claim in an early draft was wrong and is corrected in the final comment: `ripgrep` **is** packaged by Alpine on every base we build, including 3.15. The actual reason for the upstream archive is version consistency — apk gives 13.0.0 on the php7.4 base against 14.1.1 on php8.5, so a workflow's `rg` would depend on which PHP tag it ran under. Same argument the OpenTofu block already makes. ## Follow-up, not in this PR - `Someones.Computer`'s `.github/workflows/cancel-merged.yml` and `.github/actions/dispatch-workflow/action.yml` can drop their hand-rolled `curl` + `php -r` JSON filtering for `tea api` now. Separate repo, separate PR. - The Claude Code CLI (`docs/claude-in-ci-plan.html`, branch `docs/claude-in-ci-proposal`) also wants a pinned entry here with `DISABLE_UPDATES=1` / `DISABLE_INSTALLATION_CHECKS=1`. Left out deliberately — that proposal is not approved yet, and this PR should not presuppose it.
Add tea, gh and ripgrep to the runner image
All checks were successful
Build / Build Builder (push) Successful in 6m1s
f3b67bf3b1
Every forge interaction in CI is currently hand-rolled curl against /api/v1
with a manual Authorization header, because the image has no forge CLI. tea is
the one that matters: git.grey.ooo is canonical for these repos, and 0.15.0 is
comfortably past the 0.14.2 floor for `--draft` and `pulls edit --ready`.

gh is included for the repos that genuinely live on github.com. It is not an
endorsement of using it where GitHub is only a read-only mirror — README.md
spells out that caveat, since a repo in that shape gets stale or absent CI
state from gh without any warning that it has.

ripgrep is packaged by Alpine but at whatever version each base pinned (13.0.0
on the php7.4 base against 14.1.1 on php8.5), so it comes from the upstream
musl archive to keep one version across every variant.

jq and yq were already present via EXTRA_PACKAGES and are left alone.

All three are single static binaries fetched by pinned ARG and verified
against the upstream checksums file, covering amd64 and arm64.

Also clean up the build downloads. The OpenTofu and AWS blocks fetched into a
mktemp dir and never removed it, shipping ~101 MiB of archives that the
runners then re-pull on every job with force_pull. All three blocks now trap
EXIT on their scratch dir, which more than pays for the new binaries:

  uncompressed  1542.8 -> 1513.8 MiB  (-29.0)
  compressed     576.2 ->  523.7 MiB  (-52.5)

Linters are deliberately still absent — hadolint, markdownlint and actionlint
are run as pinned docker images by consumers so CI and a laptop execute the
identical build, and installing them here would break that.
geusebio changed title from Add tea, gh and ripgrep to the runner image to WIP: Add tea, gh and ripgrep to the runner image 2026-08-09 12:14:38 +00:00
geusebio changed title from WIP: Add tea, gh and ripgrep to the runner image to Add tea, gh and ripgrep to the runner image 2026-08-09 12:20:10 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
actions/Github-Actions-Runner!8
No description provided.