Why Did My Build Step Run Again, Six Cache Misses and What Memo Said About Each
A cache that misses without saying why is almost as annoying as no cache. Someone notices the step ran again, nobody knows why, and the cache key gets a random suffix “to be safe”. After that the cache never hits.
BXM Memo keeps the manifest of the last run of each named job. On a miss it compares the two and says what changed. We put that to work on a small report step: a Python script that totals sales per region from monthly CSV files into out/report.txt. The step runs through Memo with the script and data/*.csv as inputs, REPORT_CURRENCY as an environment variable that changes the result, and the Python version as the salt.
bxm-memo run -n report -i report.py -i 'data/*.csv' -o out \
-e REPORT_CURRENCY --salt "$(python3 --version)" -- python3 report.py

Then we changed one thing at a time and asked bxm-memo explain before every run. Here’s what it said:
| What changed | What Memo said |
|---|---|
| First run | miss: no earlier run of this job in the cache |
| Nothing | hit: a run with these exact inputs is cached |
REPORT_CURRENCY from EUR to CHF |
miss: env REPORT_CURRENCY changed |
python3 moved from 3.12 to 3.13 |
miss: the salt changed |
One amount corrected in data/2026-09.csv |
miss: input data/2026-09.csv changed |
data/2026-11.csv added |
miss: input data/2026-11.csv is new |
data/2026-10.csv deleted |
miss: input data/2026-10.csv is gone |
| All of it undone | hit: a run with these exact inputs is cached |
Each line names the cause. The last row matters too. Putting back Python 3.12, EUR and the original two files gave a hit, and the report came back from the first run, with “built with Python 3.12” in it. Memo keys on content, not on time or order, so going back to an earlier state finds that state’s result.
The salt line is the vague one
“The salt changed” doesn’t say what the salt was. Memo stores the salt as you gave it, but only compares it. If you want the reason to name the file, pin the version in a file and list it as an input as well. A .python-version or rust-toolchain.toml is the usual one, and then the miss reads “input .python-version changed”. Keep the salt too: the file says which version you meant, and the salt says which one actually ran.
Why this matters in CI
A miss you can explain is one you can fix. When a step keeps missing on a CI runner and the reason is “env CI_JOB_ID changed”, somebody declared an env var that changes every run. When the reason is a file nobody expected, the input pattern is too wide. Both take a minute to fix once you can see them. bxm-memo explain runs nothing, so you can ask it on a laptop or in a CI step.
Run it yourself
The script is examples/case-studies/06-miss-reasons in the repository, and the recorded output is test/results/case-studies/06-miss-reasons.txt. Every flag is on the reference page.
More examples are on the use cases page.