ADR-0017: CI installs its toolchain from a cache, with retries¶
Status: Accepted Date: 2026-10-13 Deciders: Alex Nodeland Amends: ADR-0003: how every job installs its tools
Context¶
Every job that runs moon, uv or Bun installed them through moonrepo/setup-toolchain, with auto-install: true. The action downloads proto's install script from GitHub on every run, and the script downloads proto, at its latest version, with one try. Only proto's tools and plugins are cached, never proto itself. On 2026-10-12 several jobs failed when one of those downloads answered 504, and passed on a rerun (lattice #96). A transient failure of a download nothing in the change touched failed a required check.
Decision¶
- Every job installs its tools through
.github/actions/toolchain, a composite action of lattice's own, in place ofmoonrepo/setup-toolchain. - proto is pinned, as
proto = "..."in.prototools. Renovate's proto manager moves it, after three days, as it does the other tools. - proto's archive is checked against digests pinned in the repository, in
.github/actions/toolchain/proto.sha256, by version and architecture, copied from the release's assets.- The check guards against an archive changed after it was pinned, or corrupted on the way. It doesn't vouch for the release itself: the digests are whatever the release published when they were copied.
- A bump of proto needs its digests added by hand, so Renovate's pull request fails until they are.
~/.protois cached whole: proto, its tools and its plugins, keyed by the runner's system and architecture and by.prototools.- Every job restores the cache, and a job that finds it downloads nothing. The publishing job restores none (
cache: false). - Only Nightly's check job, on
main, saves it, afterproto clean, when its key is new. So a pull request never writes a cache other jobs restore. - The AWS job's OpenTofu and TFLint, which it installs after trusting
.prototools, stay out of the key and out of the cache.
- Every job restores the cache, and a job that finds it downloads nothing. The publishing job restores none (
- What isn't cached is downloaded with retries.
- proto comes from its GitHub release, with
curl's retries, five of them, on every error. proto installis retried three times, longer apart each time.
- proto comes from its GitHub release, with
- The action runs on Linux only, as CI's runners do, and says so on any other system.
- zizmor checks the action too, in CI and in the hooks. Its
self-repositoryaudit asks foruses: $/..., which actionlint doesn't accept yet, so each use of the action ignores that audit, with the reason.
Options considered¶
| Option | For | Against |
|---|---|---|
An action of lattice's own: proto pinned, ~/.proto cached, downloads retried (chosen) |
A job with a cache downloads nothing; one without retries each download | An action to keep, of about 40 lines |
| setup-toolchain, with its cache | Nothing to keep | It downloads its script and proto on every run regardless, and its cache never holds proto |
| setup-toolchain, run again when it fails | Nothing to write | A second step that runs only after a failure, in every job; the first failure still shows |
| A runner image with the tools in it | Nothing downloaded at all | An image to build, host and keep current |
Consequences¶
- Easier: a job that finds the cache installs nothing over the network, and one that doesn't survives a transient failure.
- Easier: every job runs the same proto, and Renovate moves it.
- Harder: lattice keeps an action and a script that setup-toolchain kept before.
- Harder: a bump of proto needs its digests copied by hand.
- Revisit: the
self-repositoryignores, once actionlint accepts$/.