A build cache only grows unless something takes entries out. How vx evicts by age and by size, what it does with files no index knows, and why it refuses a bound of zero.
Caching
Caching
Every cache miss in a monorepo adds an entry: the outputs of one task,
packed and stored under its key. Nothing in that loop ever takes one out.
Left alone, a busy repository’s cache grows until the disk is full. vx
gives you two ways to bound it and does the cleanup it can do on its own.
--older-than drops entries nobody has used for that long. A hit
counts as a use, so an entry your builds keep restoring stays however
old it is. --max-size is a cap: once the age rule has run, vx evicts
the least recently used entries until the store is under the size you
gave. Use either or both. Age goes first because it is the cheap,
obvious win: whatever nobody asked for in a month is unlikely to be
asked for tomorrow.
Durations take s, m, h and d (30d, 24h). Sizes take K,
M, G and T in powers of 1024, with an optional B (500M,
1gb).
At the end of every run, after its saves and uploads have landed, vx
applies that policy. It prints nothing: housekeeping is not the run’s
news. A run with nothing to evict pays one scan of the index. A
cleanup that fails is silent and never fails the run, and a run stopped
by Ctrl-C or an abort skips it.
One thing to know about the size cap: it is least recently used first,
so a maxSize smaller than one run’s outputs evicts that run’s own
entries. Set it above what a full build saves.
vx info shows where you stand, as 812 (3.0 GB of 10 GB, 30%), and
what each run’s end will evict.
The cache is two things: an index (rows in SQLite) and the artifacts
beside it (<hash>.tar.zst). They can drift apart. A cache format
change resets the index and leaves the artifacts. A save that crashed
leaves a temp file it never renamed. vx calls these orphans.
Every prune also sweeps for them. An orphan is judged like an entry, by
its file time, because another vx version sharing the store may still
read it. A run that asks for an orphan’s key indexes it again from its
bytes and gets a hit; one no task asks for again is only reclaimed by
the sweep. Nothing younger than an hour is taken, and only names vx
writes are touched: a release.tar.zst you keep in a shared cache
directory is never deleted.
The reverse drift is handled too. If you delete an artifact by hand,
its index row goes with the next prune, and its bytes count toward
neither the size cap nor the freed total, because they are on no disk.
That would evict every entry, and a zero is far more often a value
computed by mistake than an intent. The same goes for a bare number:
--max-size 10 would mean ten bytes, so vx asks you to write 10G, or
10B if bytes really are the unit. Running vx cache prune with no
policy at all is an error too. When you really want the cache gone,
delete the store directory vx info names.
Wouldprune42entries (1.3 GB), would reap 3 orphaned artifacts (12MB)
--dry-run picks the victims under the same policy and stops there.
The real prune with the same flags removes exactly what it named. Add
--format json for a script or an agent; it prints
{ dryRun, evicted, bytesFreed, orphans, orphanBytes }.
A prune that deletes also waits for a vx run on the same workspace to
finish, so it never evicts an entry that run is restoring. A run it
cannot see, such as another workspace sharing the cache directory, is
still safe: an artifact that disappears before its restore is a miss,
and the task runs.
A task reads src/a.ts, and while it runs, something writes src/a.ts. Which cache entry gets saved? We read the source of vx, Turborepo, Nx and Vite Task to find out. Each made a different trade.
Caching
When an input moves mid-run, vx keeps the result and skips the save
A cache key describes the inputs at the moment it was taken. If one changes while the command runs, the output was built from something the key does not name. vx keeps your result, saves no entry, and names the file. Now on CI too.
In a monorepo, projects nest. A root package that globs **/* would otherwise hash, and clean, every member under it. vx draws a fence at every nested project, so a key sees only its own project, and a change elsewhere reaches it only through a declared edge.
Turborepo, Nx and other product names are trademarks of their owners. vx is not affiliated with or endorsed by them.