
Every cache key known before a task runs
Two workspace rules that let vx know every key before anything runs, servers that stay off when only cache hits need them, and a live OpenTelemetry export.
The cache moves to ~/.vx and is shared by every checkout of a repo, --affected follows task edges, and npx @vzn/vx-migrate does the whole adoption.

vx 0.0.496 to 0.0.520 shipped from October 4 to October 6, 2026. The theme
is less setup and less noise: every clone and worktree of a repo now shares
one cache, --affected picks tasks instead of packages, one command moves
a Turbo or Nx setup to vx, and the run summary is the last thing a run
prints.
In this release
Cache entries now live in ~/.vx/<id>/cache, like Nx. The id comes from
the git remote and the workspace’s path in the repo (or the first commit
when there is no remote), so clones and worktrees of one repo hit each
other’s entries. cacheDir, VX_CACHE_DIR or --cache-dir still keep the
whole cache in one directory of your choosing. A store or index from
another schema version is now reset instead of refusing the run, and its
artifacts are indexed again on their next hit, so a schema change no
longer throws the artifacts away.
import { defineWorkspace } from '@vzn/vx/config'
export default defineWorkspace({ cacheDir: 'build/.vx', // unset: ~/.vx/<id>/cache, shared by checkouts})flowchart LR
A["~/code/app"] --> C[("~/.vx/<id>/cache")]
R["worktree: app-review"] --> C
H["worktree: app-hotfix"] --> C
PRs #2773, #2775. Docs: Workspace config.
--affected now selects tasks, not packages. A changed file seeds the tasks
whose declared inputs match it, and a requested task runs when its
dependsOn closure holds a seeded one. A package with no build task gets a
default build group behind ^build, keyed on every file of the package. So a
package consumed as source still moves its dependants’ keys: here ui has no
tasks, and an edit to it re-runs app#test.
echo 'x' >> packages/ui/src/i.tsvx run test --affected=HEAD ⏺︎ 7ms success miss app#testPR #2770.
npx @vzn/vx-migrate now asks whether to migrate natively (a
vx.config.ts per package) or keep turbo.json / nx.json as the source.
--native or --keep answer it; without a terminal it is native. It
installs vx with the repo’s own package manager (--no-install skips that)
and leaves your package.json scripts alone.
npx @vzn/vx-migrate --nativePRs #2771, #2772. Docs: One command.
Nothing prints below the end-of-run summary anymore. Aborted and unstarted tasks are rows in the task list with their own status, a skipped task names its blocker, and a flaky task carries a note on its row. Warnings from uploads, plugin teardown and history land above the summary. The failure recap is gone: each failure’s output already prints above it.
◼︎ 11ms failed miss a#build ⊘ skipped b#build • blocked by a#build
┌─ a#build > failed (exit 1)
$ mkdir -p dist && cp src/i.ts dist/ && exit 1
└─ a#build ── (11ms) failed (exit 1)
─ vx 0.0.0 ─────────────────────────────────────────────────── projects ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰ 2 in run · 2 total tasks ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰ 1 failed · 1 skipped · 2 total cache ▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰▰ 1 miss · 1 skipped
info 4 workers · local cache time 56ms · max 11ms · avg 11ms · min 11ms result 2 tasks · 1 failed · 0 cached (0%) · 56msPRs #2752, #2757, #2780, #2781.
A hit either found its outputs in place (up-to-date) or restored them from
the cache. Only the live summary told them apart before. The run history
now stores the split, so vx last, vx info, vx why, the markdown
report, vx mcp and the plugins all show it.
$ vx last 2 tasks · 2 hits (0 up-to-date, 2 restored: 2 local, 0 remote)
restored-local a#build 17ms 73b25577a99ad2f8 restored-local b#build 13ms 4935028bcd991dbdCommit f336e9f.
The GitHub Actions plugin has a new package name. The github() export is
unchanged, so only the import moves.
import { defineWorkspace } from '@vzn/vx/config'import { github } from '@vzn/vx-ci'
export default defineWorkspace({ plugins: [github()] })PR #2782. Docs: GitHub Actions.
@vzn/vx-otel now sends per-task metrics: duration, CPU time and peak
memory at each task’s end, and CPU usage and memory each second while it
runs, so a metrics backend can chart them. A task span links to the tasks
it waited on, records each failed attempt as a vx.task.retry event, and
each VX_TIMING stage is a child span of the run.
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 vx run build --allPRs #2783, #2784. Docs: OpenTelemetry.
A cold run does less work per task: no deferred closure walk when nothing
is deferred, upstream outputs read only for a plugin executor, and small
artifacts packed on the calling thread. On macOS tasks run under dash
when it is on PATH. vx’s own cold CPU on 1,090 packages went from 6.85 s
to 5.7 s.
vx run build --all --forcePR #2767.
src/@(x|y).ts in a task glob matched no file, so an
edit replayed a stale hit. vx now refuses it at load and suggests the
brace form (#2768).npm_execpath for the repo’s package manager, so tools like
npm-run-all call back pnpm or bun instead of a global npm
(#2774).cache: { inputs: { files: ['src/{x,y}.ts'] }, outputs: { files: ['dist/**'] } }@vzn/vx-github is now @vzn/vx-ci; github() is unchanged.~/.vx/<id>/cache; existing entries miss once.Cache.getMany and CacheLayer.getMany take an optional get context.formatFailureRecap are removed.See Upgrading.
npm install -D @vzn/vx@latestA standalone binary updates itself with vx upgrade.