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
Caching
A cache key describes a task’s inputs at one moment. If an input changes
while the command runs, the output may have been built from bytes the key
does not describe. Save it under that key and a later hit restores an
output that matches neither version of the file.
It happens more than it sounds: you save a file while a build runs, a
code generator in another task rewrites a shared file, or a tool rewrites
its own config. We read each tool’s source, at the version named, to see
what it does after the command exits.
Turborepo computes the task hash before the task runs. After a
successful run, save_outputs globs the declared outputs and stores them
under that same hash. It does not look at the inputs again.
Source: crates/turborepo-run-cache/src/lib.rs,
save_outputs.
Nx hashes a task in processTask before it is scheduled, and after the
run postRunSteps calls cache.put(task.hash, …) with that hash. It
does not compare inputs before and after. In batch mode, a task whose
inputs include its dependencies’ outputs is hashed again after it runs,
against the files as they are then.
Source: nx@23.3.0,
dist/src/tasks-runner/task-orchestrator.js
(processTask, postRunSteps) and
cache.js (put).
Vite Task traces the files a task opens (its fspy tracer) and checks
them after the run. If the task wrote a file it also read, the entry is
not saved and the reason is InputModified. The check covers reads the
tracer saw from the task’s own processes, not the globbed inputs; its
comment notes that a task writing a globbed file it never read gets
repeated misses rather than a wrong entry.
Source: vite-plus 1.1.0 pins vite-task at
7d69d65,
crates/vt/src/session/execute/cache_update.rs.
vx takes the key just before the command and, after it exits, checks
every declared input once, whoever wrote it: the task, your editor, or
another task. Any file written after its digest was learned counts as
moved, even if its bytes are back to the original, because an edit
that was undone mid-run cannot be told apart from no edit at all. A
moved input keeps the run’s result, saves no entry, and names the file;
every task downstream skips its save too.
[vx] app#build: `packages/app/src/routes.gen.ts` changed after its key was taken — the result stands, but is not saved under a key that no longer describes it; if the task writes it, declare it in cache.outputs
How strict this is, is yours to choose with cache.inputStability:
'mutable' saves when the bytes end identical (for a tool that rewrites
its own package.json), 'unchecked' turns the check off, and
severity: 'error' fails the task instead of warning.
Source: packages/vx/src/orchestrator/execute-task.ts; the full story is
in When an input moves mid-run.
Checking costs something. vx pays one lstat per input after a run that
saves, and a task that touches its own inputs rebuilds next time unless
you tell vx how to judge it. Turborepo and Nx skip that work. Vite Task
checks what its tracer already recorded. None of these is free, and
which trade fits depends on how often your inputs move under a running
task.
We chose to make a cache entry mean exactly what its declared inputs say,
and to make the strictness a setting. vx is at
vznjs.github.io/vx.
Keep reading
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.
Files, env vars, tool versions and upstream tasks go into the key. Outputs and both output streams come back. A warm hit restores nothing at all, and a damaged artifact is a miss, never a wrong build.
Turborepo, Nx and other product names are trademarks of their owners. vx is not affiliated with or endorsed by them.