Skip to content
vxvx
GitHubBlueskydev.toRSS

vx cache clean

vx cache clean evicts old or oversized cache entries, or with no bound clears the cache. It operates on the entries and <hash>.tar.zst artifacts of the store the workspace’s index records (~/.vx/<id>/cache, shared by every checkout of the repository), or of <cacheDir> itself when the workspace names one (cache.dir, VX_CACHE_DIR, --cache-dir).

vx cache clean --older-than <duration> # Drop entries last used before now - duration (up to 1h slack).
vx cache clean --max-size <size> # After age-based pruning, evict LRU until under <size>.
vx cache clean ... --dry-run # Say what either policy would reap; delete nothing.
vx cache clean ... --format json # { dryRun, evicted, bytesFreed, orphans, orphanBytes }
vx cache clean ... --cache-dir <path> # The cache a run with the same flag uses.
vx cache clean # No bound: clear the whole cache (below).
vx cache clean --global # Also clear every other repository's store.
$ vx cache clean --older-than 30d
✓ vx cache clean freed 1.3 GB
42 entries
$ vx cache clean --older-than 30d # after a SCHEMA_VERSION bump
✓ vx cache clean freed 1.3 GB
42 orphaned artifacts
$ vx cache clean --older-than 30d --dry-run
› vx cache clean would free 1.3 GB (dry run)
42 entries · 3 orphaned artifacts
Option Meaning
--older-than <duration> drop entries last used before now minus the duration
--max-size <size> then evict the least recently used until under the size
--dry-run say what would go; delete nothing
--global with no bound, also clear every other repository’s store
--format json the result as one object
--cache-dir <path> the cache a run with the same flag uses

Both bounds may be combined: age-based eviction runs first, then LRU eviction if the total is still over the size cap. No run evicts anything: to bound the cache, run this from a schedule or CI.

A clean with a bound also drops run history and hash memos older than 30 days; a run never does.

Both flags take either form: --older-than 30d or --older-than=30d.

Duration units: s, m, h, d, case-insensitive. Examples: 30d, 24h, 60m, 30s, 30D.

Size units: K, M, G, T (powers of 1024), case-insensitive, with an optional B or iB (512MB, 32KiB). Optional B suffix is accepted. Examples: 500M, 1G, 100K, 2T, 500MB, 1gb. A bare number is refused here: --max-size 10 would read as ten bytes and evict nearly everything, and nobody means that — write 10G, or 10B when bytes really are the unit.

A zero bound is rejected. --max-size 0 and --older-than 0d would evict every entry in the cache, which is far more often a computed-to-zero retention than an intent. vx cache clean with no bound clears the whole cache when that is really what you want.

After eviction, prune sweeps the cache directory for orphans: an artifact (a <hash>.tree directory, or a <hash>.tar.zst) the index has no row for (a SCHEMA_VERSION bump drops every table and leaves the artifacts behind, as a deleted cache.db does) and a <hash>.tree.tmp-* or <hash>.tar.zst.tmp-* a save that crashed never renamed. A run that asks for an orphan’s key indexes it again from its bytes and hits; one no task asks for again is only reclaimed here. The policy judges a row-less artifact as it judges an entry, by its file time plus an hour (a hit renews a file time over an hour old): another vx version sharing the store may still use it. A temp goes once it is an hour old; nothing younger than an hour is taken. Only the names vx writes are taken: <hash> is the key’s 16 lowercase hex digits and the temp suffix is the one a save makes, so a release.tar.zst beside the index in a cache.dir you share is never touched (item 968). The converse, an index row whose artifact is gone (deleted by hand), is dropped by the same prune, and its bytes count toward neither --max-size nor the evicted total: they are on no disk, and counting them evicted real entries to make room for them (item 975). A row used within the hour is kept until a later prune, but its bytes are left out all the same (item 1081). The sweep runs on every prune, under either flag, and reports separately from the policy’s evictions.

--dry-run picks the victims under the same policy and counts the orphans the sweep would take, then returns without touching the index or the directory; the real prune with the same flags reaps exactly what it named (an in-flight save aside). On an index an earlier vx wrote, which the real prune resets first, it reads the index as that reset leaves it: the shared store’s entries still face the policy, and every artifact in the workspace’s own directory is row-less, judged by the policy on its file time (item 1083).

A prune that deletes waits for a vx run on the same workspace to finish first (the run’s lock; it says [vx] waiting for another vx run (pid N) on this workspace to finish… after a second), so it never evicts what that run is restoring. One started by a task of that run is refused instead: the run holds the lock until the task ends. A run it cannot see — another workspace sharing the --cache-dir — survives a prune anyway: an artifact that vanishes before its restore is a miss, and the task runs (caching).

vx cache clean resolves the workspace root from cwd and honors a defineWorkspace({ cache: { dir } }) override — it prunes the same directory a run would use. --cache-dir <path> (cwd-relative, as on vx run) names another one, so a run that wrote elsewhere can be pruned there.

clean is the only vx cache subcommand: the statistics other runners put under a cache verb — the directory, the entry count, the size — are part of vx info, and vx cache stats, list, ls, gc and their neighbours say so (list also names vx last --list; clear, purge, reset and wipe name a bare vx cache clean) rather than printing a bare “unknown subcommand”.

vx cache clean with neither --older-than nor --max-size clears the whole cache: the cache directory (.vx/cache, or cache.dir / --cache-dir: the index and this workspace’s run history, so vx last and vx why start over) and the store its index names, where the entries live (~/.vx/<id>/cache, shared by this repository’s checkouts, so their entries go too). --global also clears every other repository’s store under ~/.vx (~/.vx/*/cache); it takes no bound and no --cache-dir. .vx/workspace.ts, the lock and .vx/.gitignore sit outside them and stay; a cache.dir that holds them (.vx, the workspace root) is refused. A cache.dir you named may be shared (build/, ~/.cache), so there only what vx wrote goes (the index, its artifacts, run history, its * .gitignore); your own files stay, and the directory goes only if that empties it. A directory with no cache.db is not a vx cache and is left alone, and the store an index names goes only if it is one (.vx/<id>/cache holding a store.db): the index is a file in the checkout, which a commit or a restored CI cache could point at any directory. A workspace that never ran says › vx cache clean nothing to free. The next run misses every task once and saves again.

$ vx cache clean
✓ vx cache clean freed 1.4 GB
.vx/cache
/home/me/.vx/3f2a9c01b4d5e6f7/cache
$ vx cache clean --global --dry-run
› vx cache clean would free 3.1 GB (dry run)
.vx/cache
/home/me/.vx/3f2a9c01b4d5e6f7/cache
/home/me/.vx/9b8e7d6c5a4f3e2d/cache

It waits for a vx run on this workspace to finish first (the run’s lock), as a trim does. With --format json it prints { dryRun, cleared, bytesFreed } (schemas/cache.json), cleared listing the directories.

Code When
0 pruning completed (zero or more entries evicted). A workspace that never ran has no cache: › vx cache clean nothing to free, and nothing is created
1 parse error, missing policy, or workspace-discovery error
Turborepo, Nx and other product names are trademarks of their owners. vx is not affiliated with or endorsed by them.