The README covers the GitHub Actions cache recipe. This guide collects the rest of the CI story: persistence recipes for GitLab CI and CircleCI, the artifact flow for getting history onto your machine, and keeping a cached history bounded.
The shape is the same everywhere: each CI job starts fresh, so
persist .temper/ across runs to accumulate history. What differs
per provider is cache semantics: GitHub Actions and CircleCI cache
entries are immutable (save under an always-fresh key, restore by
prefix), while GitLab updates a mutable key in place.
GitLab CI
A per-branch key is enough, since the job updates the same cache
entry when it ends. fallback_keys seeds new branches from the
default branch's history:
test:
script:
- mix test
cache:
key: temper-$CI_COMMIT_REF_SLUG
fallback_keys:
- temper-$CI_DEFAULT_BRANCH
paths:
- .temper/For parallel: jobs, give each node its own cache lineage and its
own history partition:
parallel: 3
variables:
MIX_TEST_PARTITION: $CI_NODE_INDEX
cache:
key: temper-$CI_COMMIT_REF_SLUG-$CI_NODE_INDEX
fallback_keys:
- temper-$CI_DEFAULT_BRANCH-$CI_NODE_INDEX
paths:
- .temper/Depending on runner configuration, GitLab caches can be runner-local: when runners do not share cache storage, history accumulates per runner and flakes take longer to surface. The artifact flow below does not have this problem.
CircleCI
Cache entries are immutable, like GitHub's: save under an
always-fresh key (epoch keeps it unique), restore the most recent
one by prefix:
- restore_cache:
keys:
- temper-{{ .Branch }}-
- temper-
- run: mix test
- save_cache:
key: temper-{{ .Branch }}-{{ epoch }}
paths:
- .temperWith parallelism, add {{ .Environment.CIRCLE_NODE_INDEX }} to
both keys and set MIX_TEST_PARTITION from CIRCLE_NODE_INDEX, so
each node keeps its own cache lineage and history partition.
Getting the history into your hands
Caches keep history between runs, but they are best-effort (entries
expire, a miss silently resets history) and you cannot browse them.
For a history that is durable and inspectable, also upload .temper/
as a build artifact — GitHub Actions:
- name: Upload test history
if: always()
uses: actions/upload-artifact@v4
with:
name: temper-${{ matrix.partition }}
path: .temper/On GitLab, add .temper/ under artifacts:paths; on CircleCI, use
store_artifacts.
Download the artifacts (from one run or several), then merge them into one deduplicated file and report on it:
$ mix temper.merge --output .temper/history-0.jsonl "artifacts/**/history-*.jsonl"
$ mix temper.report
Overlapping cache restores and re-downloaded artifacts duplicate
lines; mix temper.merge writes each recorded event once. The file
format itself is a documented contract (see the
History Schema guide), so the merged file is
also plain material for your own
scripts: it is JSON Lines, one test outcome per line.
Keeping cached history bounded
A cached .temper/ grows on every run. Prune it in CI before the
cache is saved, or occasionally from your machine:
$ mix temper.clean --older-than 90
$ mix temper.clean --keep-shas 50
--older-than keeps the last N days of records, --keep-shas the
records of the N most recently recorded commits; given together, a
record must satisfy both to survive. Pruning rewrites files in
place, so the cache saved afterwards carries the trimmed history
forward.