Skip to content
GitHubRSS

src/cli/cache.ts — vx cache prune

Implements vx cache prune. Both parsers are util/size.ts’s, re-exported here: the workspace’s cacheRetention field reads the same spellings the flags do. Drives Cache.prune({...}) from src/cache/cache.ts against the cache directory a run would use.

export async function cacheCmd(args: readonly string[]): Promise<number>
interface PruneArgs {
olderThanMs?: number
maxBytes?: number
dryRun?: boolean
format?: 'pretty' | 'json'
cacheDir?: string // `--cache-dir`: prune the cache a run with the same flag uses
error?: string
}
export function parsePruneArgs(args: readonly string[]): PruneArgs
export { parseDuration, parseSize } from '../util/index.js'
vx cache prune --older-than <duration>
vx cache prune --max-size <size>
vx cache prune --older-than 7d --max-size 500M # both
vx cache prune --max-size 1G --dry-run # say what the policy would reap; delete nothing
vx cache prune --max-size 1G --format json # schemas/cache.json
vx cache prune --older-than 30d --cache-dir <path>

At least one of --older-than / --max-size is required, and neither may be zero — a policy that would evict every entry is refused with “delete the cache directory instead”. A bare number for --max-size is refused too: --max-size 10 would read as ten bytes and evict nearly everything; give a unit. Both policies may be combined: age-based eviction first, then LRU eviction if the total is still over the size cap. The prune also reaps orphaned artifacts (an archive with no index row) and stale temps, and says so — Pruned 12 entries (1.4 GB freed), reaped 3 orphaned artifacts (…); a dry run prints Would prune … and would reap.

The directory is the one a run would use — --cache-dir, defineWorkspace({ cacheDir }) and a config plugin’s edit of it, through cliCacheDir — or a prune silently no-ops against the wrong path. A cache this user cannot write is refused up front with the directory named, as a run refuses it (a dry run only reads, and reads a read-only cache fine), and an upgrade that reset the index is announced once. A prune that deletes takes the workspace’s run lock first (acquireRunLock, orchestrator.md), so it waits for a run on the workspace — [vx] waiting for another vx run (pid N) on this workspace to finish… after a second — instead of evicting the hits that run has just probed; the age cutoff is taken before the wait, so what the run touched survives it. A dry run takes no lock.

/^(\d+)([smhd])$/i

Returns ms. s (× 1000), m (× 60_000), h (× 3_600_000), d (× 86_400_000), case-insensitively (30D is thirty days). null on parse fail; the caller surfaces invalid duration: <value> (e.g. 30d, 24h, 60m).

/^(\d+)([KMGT])?B?$/i

Returns bytes. Multipliers are powers of 1024 (K, M, G, T). Optional trailing B is allowed. null on parse fail — including digits past 2^53, which would parse to a number the user did not type; the caller surfaces invalid size: <value> (e.g. 500M, 1G).

tests/cli.test.ts: parseDuration and parseSize units and parse failures; vx cache prune with no policy errors, reports 0 entries pruned from an empty cache, and rejects an unknown subcommand. Eviction, orphan reaping and dryRun are tested in tests/cache.test.ts against Cache.prune.