
Flaky retries, sealed sandboxes
A known-flaky task gets one more try, on any machine. A sandboxed task can be sealed off from the machine and reads the outputs it depends on. cache: true is a switch, and outputs stand on their own.
Every line vx prints comes from one kit, and a run, vx last and --dry share one task grid. vx lock records each project's dependencies, and runs key from that record.

Everything vx prints now looks the same: one set of panels for a run,
vx last, --dry, every verb and every plugin. Your dependencies join
your configs in the lock, so a run never parses a lockfile.
This release is breaking. Read the breaking changes first.
In this release
Each verb, the run screen and each plugin wrote its own lines, so the
same fact looked different in vx run, vx why and a plugin’s verb.
Now one kit draws every line: panels stacked top to bottom, each a
title rule and its lines, written into the scrollback. Nothing to
press, nothing hidden. vx last replays a run as the run printed it:
$ vx last─ Run 01a1aabd-5c57-723f-9e6f-9019adbec66f ─────────────────── status ok command vx run build test --all when 2026-11-05T08:04:59.101Z · 261ms · master @ 0c1491ae tasks 3 tasks · 0 hits
─ Tasks 3 ──────────────────────────────────────────────────── time ±usual status cache cpu mem outputs blocks worker task ⏺︎ 8ms −5ms success miss 47% — 4B 2 local lib#build ⏺︎ 4ms +0ms success miss 92% — 4B 1 local app#build ⏺︎ 201ms −1ms success no-cache 1% — 0 local app#testA cell vx cannot know shows —. On Linux, mem is each task’s own
peak, read while it runs. Any CI log shows the same coloured panels,
minus the live redraw.
Plugins draw with the same kit, exported as
ui from @vzn/vx.
See vx last and Writing plugins.
vx run --dry printed its own list, so a plan read differently from
the run it planned.
Now it is a Plan panel in the run’s grid. Each row says what the run
would do, its usual time and where it would run:
$ vx run build test --all --dry─ Plan 3 ───────────────────────────────────────────────────── time usual status cache cpu mem outputs blocks worker task ⇢ cached local 1 local app#build ⇢ cached local 2 local lib#build ⏺︎ ~202ms run no-cache 0 local app#test
tasks 3 planned · 2 cache hits (2 local) · 1 no-cache predicted ~202ms wall · ~202ms total executionSee vx run.
A run parsed your package manager’s lockfile to key each task, every time.
Now vx lock also writes .vx/dependencies.lock.json: each project’s
edges and a hash of its dependencies. Commit it beside
.vx/projects.lock.json.
A run keys from the record and never parses
the lockfile. After an install, run vx lock:
$ bun install$ vx run build --all✗ vx run bun.lock changed since `vx lock`: run `vx lock`$ vx lock✓ vx lock 2 project configs → .vx/projects.lock.json--affected reads the base commit’s record, so a deleted project
selects only the projects that used it, not every project.
vx lock --check fails when the record is missing or stale.
file: or link: dependency outside the repository is hashed like
one inside it, so an edit there runs the task again.vxCache(), vxHistory() or dte() URL prints its
user:pass@ as ***.issuer/repo/branch folders, so
deleting one prefix prunes one branch.--affected in a merged results pipeline compares with the
target branch, and a merge train car with the car ahead.--filter, a dependsOn naming a task its project lacks.vx upgradeRun vx lock after upgrading and commit .vx/. The first run resets
run history (schema v51; artifacts stay).
VX_E_LOCK) when your lockfile changed since vx lock or .vx/dependencies.lock.json is missing..vx/projects.lock.json is version 5.vx show, vx why, vx last, vx init, vx run --dry and the run summary changed; --format json did not. vx last --format json task rows gain outputBytes, blocks and queuedMs.waited column → removed. Logger.taskStart(node, info?) → taskStart(node).ui → the new kit: report(title, rows), table(columns, rows).vxCache() scope on the wire is its ref, not its hash: deploy the cache server and vxCache() together. Artifacts stored before are not read again.See also Upgrading.