---
key: ci.build-once-rejected
title: '2026-07-18 — CI build-once was measured and rejected; keep the #420 tree-skip'
status: active
since: '2026-07-18'
supersedes: none
superseded-by: none
rule: 'CI build-once (a shared compile artifact across jobs) was implemented, measured, and rejected for a 40-85% wall-clock regression; keep the #420 cross-run tree-identity skip instead.'
signals: 'CI build-once, artifact tar/transport cost, tree-identity skip · paths: `docs/ci-cd.md` → Cross-run tree-identity skip · issues: #420, #398, #455'
mechanics: 'docs/ci-cd.md → Cross-run tree-identity skip; PR #455 measurement'
---

Build-once (a `compile` job producing a single artifact, consumed by `test`/`migrations`/
`functional-e2e` via `--no-build`) was fully implemented and went **green on CI** (PR #455, run
830), then **rejected on measurement**: it traded a ~12% slot-occupancy saving for a ~40–85%
**per-run wall-clock regression**.

- **Why it regressed.** The `bin`+`obj` artifact is 2.5 GB raw / 972 MB gz; tar alone costs ~82s CPU
  plus ~180s transport, consuming most of the ~465s the shared compile was meant to save. Worse,
  `compile` serializes **before** `migrations`' long ef-replay, which is pure DB work a shared build
  cannot shorten — the bottleneck was never the redundant compiles.
- **Incidental finding worth recording.** `actions/upload-artifact@v4` does not work on this Gitea
  instance — it throws `GHESNotSupportedError`, because the `@actions/artifact` v2 client library
  rejects any non-`github.com` host. `@v3` is required for any future artifact use here.
- **Kept: the #420 cross-run tree-identity skip** (`docs/ci-cd.md` → Cross-run tree-identity skip).
  It is independent of build-once — it only *skips* redundant work on identical-tree main pushes, at
  zero wall-clock cost, rather than trying to share a build across jobs. Don't re-attempt build-once
  unless the runner's artifact storage or network changes materially.

Refs: #398 (closed), #420, PR #455.
