Repository navigation
perf(ci): one CI job by default; ci-ok as a commit status; timeouts at ~2x averages - #67
Merged
Merged
Conversation
jayleaton
marked this pull request as ready for review
October 22, 2026 11:48
…outs at ~2x averages Jobs bill rounded up to whole minutes, so a 19 s docs-only PR billed 4. - ci-ok is a commit status posted by the jobs on the commit they tested (pending, then success/failure), not a job: skipped or cancelled jobs and drafts can never satisfy it, and no aggregator job is needed. - One ci job plans and, for small changes, does everything: docs-only posts success, mobile-only runs the app checks, up to 8 affected test packages run lint + tests in that job, plus the mobile or deploy/ module checks when those changed. Larger PR sets and shared-input changes fan out to lint + test (db) and test (other). Pushes to main, merge queue and manual runs skip the planner: ci does the lint + DB half and test (other) runs in parallel. - Drafts start no jobs; each run reports on the PR head of the merge commit it checked out, which removes the race the draft check guarded. - The separate deploy job (a billed minute on every run) is folded into the jobs above as inline checks. - Shared steps live in .github/actions/check. - Timeouts are ~2x measured averages, on the steps that do the work and cache-aware (lookup-only probe): warm 3 min, cold 6 (PR single job) or 8 (full/fan-out), mobile/deploy-only 2; cache restores and the toolchain 1 min; stalled cache downloads fall back to a miss after 1 min; DB tests wait at most 45 s for Postgres. Job timeouts are backstops. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jayleaton
force-pushed
the
perf/ci-billing-shape
branch
from
October 22, 2026 12:05
7a13ee5 to
3f47100
Compare
…n fallback Parallel shards mostly bought rounding waste (each job bills rounded up to a whole minute). The ci job now checks the affected packages (all of them on main, merge queue and manual runs) plus the mobile and deploy/ checks in one job; one build/lint cache keyed by commit. Fan-out is opt-in: a ci:fanout PR label or the fanout input of a manual run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cold Measured as one job: all packages warm 138 s (-> 5 min), cold 312 s (-> 11); affected sets warm up to ~75 s (-> 3), cold 131-159 s (-> 6). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hat result PR runs record the tested merge tree in their ci-ok description. A push to main looks up its PR (commit association or the "(#N)" squash title) and, if that PR's latest ci-ok is a success posted by github-actions[bot] for exactly the pushed tree, posts ci-ok success from it and skips the checks. Main moved in between (tree differs), a missing or non-success result: the push runs as before. Caches PRs restore are refreshed by a scheduled run every 4 hours (skipped when main has not moved) and by main pushes that do run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Covers main fast-forwarding to a merge-queue commit CI already tested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
GitHub bills every job rounded up to a whole minute, so CI cost was driven by job count. A docs-only PR billed 4 minutes for ~19 s of work, and the parallel shards mostly bought rounding waste. No job had a timeout under 10 minutes. JJ's decision: one CI job by default for every PR (and push), sharding only as an opt-in fallback, timeouts at most ~2× measured averages.
How
ci-okis a commit status, not a job. The job posts it on the commit it actually tested: pending, then success or failure. No job carries the name, so skipped or cancelled jobs and drafts can never satisfy it, and no aggregator job is needed. Statuses come fromgithub-actions[bot];mainhas no branch protection yet, so require theci-okstatus check when enabling it.Drafts start no jobs. The draft check job is gone: each run reports on the PR head of the merge commit it checked out (
HEAD^2), which removes the "ready run lands on the old head" race.One job checks everything a change affects: affected packages only on PRs, all packages on main, merge queue and manual runs. The mobile app and
deploy/module checks run inline only when their paths change. That replaces the separatemobile,changes,deploy,ci-okand shard jobs.Postgres is started in the background only when an affected package's tests use it. DB-backed test binaries wait for it through an
-execwrapper, so the boot overlaps compilation. This stays faster than aservices:container, which blocks the job start for 10–16 s.Fan-out is an opt-in fallback only: label a PR
ci:fanout(label created; takes effect on the PR's next push), or use thefanoutinput of a manual run once this is on main. Manual-run inputs are validated against the default branch's workflow. Measured: an all-packages run is ~2–3× the warm PR average (153 s warm, 323 s cold), so fan-out is there when wall time matters more than minutes.A push to main is not tested again when its tree already passed CI (
scripts/ci-reuse.sh):ci-okdescription (success tree=<sha>).(#N)squash title.ci-okis a success posted by github-actions[bot] for exactly the pushed tree. Then it postsci-oksuccess ("reused PR #N") and runs nothing else: 1 billed minute instead of 3.ci-oksuccess, as when main fast-forwards to a merge-queue commit, is skipped too.One build/lint cache (
v7-go-*) keyed by commit: saved on pushes to main, restored by prefix everywhere else.Shared steps live in
.github/actions/check.Timeouts at ~2× measured averages, on the step that does the work. The job can't know at start whether a run is full or cold, so its 13-min timeout is only a backstop. A
lookup-onlyprobe decides warm or cold:SEGMENT_DOWNLOAD_TIMEOUT_MINS, was 10); DB tests wait at most 45 s for Postgres (2× its 12–22 s boot).Rebased on feat(deploy): one-config Railway setup with Go IaC (deploy/platform.yaml) #39 and feat(ui): UI overhaul with shared uikit components and light/dark design tokens #66. feat(deploy): one-config Railway setup with Go IaC (deploy/platform.yaml) #39's separate
deployjob (a billed minute on every run) is folded into the single job, andpackages/uikitis in the templ list. Nothing is renamed here (the Go Fast Stack rename comes separately).Evidence
Real Actions runs. PR scenarios use the full lifecycle: open as draft → push to the draft → ready. All scratch PRs (#56–#65, #68–#72) are closed, with branches and caches deleted by recorded name.
pushtrigger forscratch/foundation-reuse-push-*and passed CI, recordingsuccess tree=7515882f…. That equals GitHub'srefs/pull/73/mergetree.… (#73)) with exactly that tree pushed to a scratch branch ran 16 s / 1 billed min. The log showedPR #73 … ci-ok=success … reusing that result, and the commit gotci-oksuccess "reused PR scratch: CI reuse proof (do not merge) #73 (tree 7515882f0721)".no PR result for tree 9a0055ab…: running the checksand ran everything: 333 s / 6 min. It was cold because a non-main branch can only restore main's caches, which have nov7key yet.ci-reuse.shon that commit afterwards found its own success and skipped.TestAdminManagement(apps/admin/internal/server) failed in 2 of 3 all-packages runs here. It expects eve's audit trail in a fixed order, and two audit entries sometimes come back swapped. Re-runs passed, and main's recent runs are green. It belongs to the admin owner.ci-oksuccess, 231 s / 4 min.ci-okfailure; drafts post nothing. Superseded or cancelled runs that GitHub lets finish post success only for a fully tested tree; an interrupted run posts failure.ci:fanoutis the escape hatch.Projection at 200 PRs/day
Assumed mix: 10% docs, 5% mobile, 70% small/multi Go, 15% all-packages. Each PR is followed by one merge push to main. A main push costs 1 min when reused and 3 min when it runs. The scheduled cache refresh adds 6 runs/day (~3 min each, 1 min when main hasn't moved).
"Up to date" means main didn't move between the PR's last CI run and its merge. With a merge queue, main fast-forwards to the tested queue commit, which is skipped by its own status.
Checks: actionlint is clean. No Go code changed; local checks are limited to script and workflow linting (compute rule).
Merge Danger
Door: two-way. Only CI changes.
Blast Radius: CI.
ci-okis now a status, not a check run. Any future branch protection should require theci-okstatus.ci.lint + test (db)/test (other)appear only with the opt-in fan-out.v7-gocache key): a main push ~5.5 min / 6 billed, then warm.🤖 Generated with Claude Code