A cache hit now rewrites only the outputs that changed, in place, and writes nothing a task did not declare. A local hit restores while its dependencies still run. A generated client in node_modules can be an input, vx upgrade -g moves only the global vx, and only vx decides what reaches a remote cache.
A cache hit no longer empties your dist/ before it fills it again. It
rewrites only the files that changed, in place, so a dev server or a
test reading the outputs never finds one missing. A hit in the local
cache also restores at once, beside the tasks it depends on, even with a
remote declared.
Read the breaking changes before you update if a CI
keeps pull requests off a Turbo or Nx remote cache with their own
variables, or a task compiles with bun build --compile.
A hit deleted every declared output, then wrote the cached ones back.
Anything reading dist/ in between, a dev server, a watcher or a test,
found files missing. A restore that failed halfway, on a full disk, left
the outputs gone. Every file also got a new inode, which woke watchers
for files that had not changed.
Now each cached file is written to vx’s own .vx/tmp/ and renamed over
the old one, so a reader sees the old file or the new one, never
neither. A file that already holds the cached bytes stays where it is.
vx compares bytes, not timestamps, because reproducible builds give
every file the same mtime. Files the entry does not hold are removed
only after every rename has landed, and a restore that fails leaves the
outputs as they were.
a.ts is back to the cached bytes, old.txt is gone, and b.ts and
client.txt were not touched.
A restore also writes only what the task declares. Each file in the
entry must match the task’s cache.outputs, and never sits under
.git, .vx, a vx.lock.json or a node_modules no glob names. One
file outside that set and nothing is written: the entry is dropped and
the task runs. Remote execution with @vzn/vx-reapi writes through the
same check. See Caching.
With a remote cache declared, a task whose entry sat in the local cache
still waited for every task it depends on before it restored. On one
real repo a build waited about 18 s on a types task, then restored in a
moment. With no remote, the same hit restored right away.
Now vx asks the local cache what it holds before the run starts, with or
without a remote. A local hit restores at once, beside the tasks still
running. A local miss asks the remote in the background, as before, so
no network request delays the first task.
Here build depends on an uncached codegen that takes two seconds:
Terminal window
$rm-rfapp/dist
$vxrunbuild--all &
$sleep1 && lsapp/dist# codegen is still running
a.tsb.tsclient.txt
On 0.0.640, app/dist was still missing at that point.
prisma generate writes its client to node_modules/.prisma/client. A
build that listed it as an input never missed when the client changed:
vx dropped every file under node_modules, even a path the config named
outright, and replayed the old output.
Now a path under an ignored node_modules that an input names is read
from disk and hashed, like any other ignored file you name. A broad glob such as
src/** still never walks node_modules.
Inside a project, a global vx hands every call to the project’s own vx,
so vx upgrade always moved the project’s dependency and plugins too.
There was no way to update only the vx on your PATH.
vx upgrade -g (or --global) upgrades only the global vx, the way you
installed it: a verified binary, or a global add with npm, pnpm, bun or
yarn. It never touches a package.json or a plugin. Plain vx upgrade
works as before.
Terminal window
$vxupgrade-g
›vxupgradeglobal0.0.640→0.0.641
npminstall-g@vzn/vx@0.0.641in/usr/local/lib
The repo stays on its own release until someone runs vx upgrade there.
Run as the project’s own vx, -g refuses and says to install a global
one. See CLI.
turboCache() and nxCache() also obeyed Turbo’s and Nx’s own
switches, and any remote plugin could narrow vx’s policy, so the same
workspace behaved differently depending on which tool’s variables a CI
set.
Now a cache plugin only moves bytes. turboCache() no longer reads
TURBO_CACHE or TURBO_REMOTE_CACHE_READ_ONLY, and nxCache() no
longer reads NX_SKIP_REMOTE_CACHE or NX_DISABLE_REMOTE_CACHE. Which
runs read or write the remote is vx’s cache.policy alone. A server
that refuses an upload, such as one behind a read-only token, still
turns uploads off with one warning.
# a fork's pull request: read the shared cache, never write it
- run: vx run build --cache-policy=local:rw,remote:r
vx init installs every @vzn/vx-* plugin .vx/workspace.ts
imports, in one install with vx. Re-run in a repo it had set up, it no
longer fails at the lock with cannot find '@vzn/vx-lockfile'.
A project in a directory whose name holds ? loads. Bun read the rest
of the path as a query, so vx lock failed for the whole workspace.
The run footer counts every task whose log or outputs reached the
remote under remote ↑.
turboCache() and nxCache() → no longer read TURBO_CACHE,
TURBO_REMOTE_CACHE_READ_ONLY, NX_SKIP_REMOTE_CACHE or
NX_DISABLE_REMOTE_CACHE. A CI that kept untrusted pull requests off
the shared cache with them sets --cache-policy=local:rw,remote:r or
cache.policy instead.
Bun’s compile temp files → no longer left out of every input by core.
A **/* input on a bun build --compile task, or on a task in the
same directory, adds '!**/*.bun-build' and
'!**/.????????????????-????????.tmp/**'.
A cached entry that holds a file outside the task’s declared outputs
→ a miss, and the task runs. If a task writes more than its
cache.outputs globs name, those files are no longer restored.
Plugin API: the access field a remote cache plugin returned → removed.
Cache.restoreOutputs and CacheLayer.restoreOutputs take the task’s
declared outputs as a fourth argument, and write only what they select.
Inputs move onto the task, so every task is keyed whether it caches or not. A gitignored file or a symlink you name is an input. A test can read what its build wrote, vx upgrade moves your plugins with it, and a run waits on less.
A live run now shows what vx itself is doing and what each idle worker waits on, the footer counts remote failures and uploads in flight, --force no longer re-uploads what the cache holds, vx lock refuses fewer configs that were fine, and vx run sheds nine flags.
Every vx verb now speaks in one shape and says how to fix what it refuses, a CI run keeps the logs and outputs you need to debug it, a new branch forecasts from main's timings, and a run your local cache answers stays off the network.
Turborepo, Nx and other product names are trademarks of their owners. vx is not affiliated with or endorsed by them.