Repository navigation
v0.0.646
"Setup steps as dependencies, only vx's own verbs"
A setup step such as a migration or a seed can now be a dependency. A
dev server stays down when the remote cache already holds every test
that needs it. vx init gives ^build its package edges in one step,
and vx why names the task that keeps a key from ever hitting. vx now
answers only its own flags and verbs.
This release is breaking: other runners' flag aliases and command hints
are gone. Read the breaking changes first.
Enjoy the release!
In this release
- A setup step can be a dependency
- A server stays down behind remote hits
- Package edges right after vx init
- vx why names the task that stops a hit
- vx speaks only vx
- Other noteworthy changes
- Breaking changes
A setup step can be a dependency
A persistent task that exited 0 before its readyWhen matched used to
fail, so a migration, a seed or docker compose up --wait could not sit
in front of the tests that need it.
Now a persistent task that exits 0 before it is ready counts as ready,
and its dependants run. One that exits non-zero still fails.
// vx.config.ts
import { defineProject } from '@vzn/vx/config'
export default defineProject({
tasks: {
db: {
exec: {
command: 'echo migrated',
persistent: { readyWhen: 'listening' },
},
},
test: {
dependsOn: ['db'],
exec: { command: 'echo tests passed' },
},
},
})$ vx run test
┌─ //#test > $ echo tests passed
tests passed
└─ //#test ── (3ms) success
tasks 2 success · 2 totalSee persistent tasks.
A server stays down behind remote hits
vx run e2e already left the dev server down when every test it serves
was a local hit. On a fresh CI runner the hits come from the remote, so
the server started anyway.
Now vx asks the remote about those tests first. When it holds them all,
the server never starts; at the first miss it starts, and that test runs
once the server is ready.
flowchart LR
E[e2e tests] -- "keys asked up front" --> R[(remote cache)]
R -- "all held" --> S[server not started]
R -- "a miss" --> U[server starts, the test runs]vx run --dry marks such a server not started.
Package edges right after vx init
In a monorepo with a lockfile, vx init declared no plugin, so the
first dependsOn: ['^build'] was refused at vx lock with "needs
project edges".
Init now declares the plugin the root lockfile calls for (bun(),
pnpm(), yarn() or npm()) and installs @vzn/vx-lockfile beside
@vzn/vx. Beside a bun.lock:
// .vx/workspace.ts
import { defineWorkspace } from '@vzn/vx/config'
import { bun } from '@vzn/vx-lockfile'
export default defineWorkspace({ plugins: [bun()] })Every next: line that init and the migration print now names bare
vx, since init installs vx globally first. See the
quickstart.
vx why names the task that stops a hit
A key that folds a task with no inputs is new on every run, so the
task never hits. vx why said "inputs differ" and named neither the
cause nor the task to fix.
Now the run records that task, and the verdict names it:
$ vx why build
//#build — run 01a16609-2047-74f3-b570-a3194f1c3cf7
this run 2026-10-22T23:53:56.590Z · success · executed · key a2e1acbece94cd3e
previous 2026-10-22T23:53:56.400Z · success · key 6957e3f8748658ac
verdict never cached: its key folds //#stamp, which declares no `inputs`, so each run derives a new key; declare inputs on //#stamp
detail //#stamp declares no `inputs`, so no fingerprints are kept to diffvx speaks only vx
vx owns its commands. vx run no longer rewrites another runner's
flags (-t, -p, -F, --skip-nx-cache, --dry-run), and verbs such
as vx run-many, vx ls and vx reset no longer answer with a
pointer. Each is an unknown flag or command, like any other typo:
$ vx run build -t x
✗ vx run unknown flag: -t
see vx run --helpvx run --format json now says where a run's JSON is: vx last --format json after the run, or --dry=json for the plan.
Other noteworthy changes
- On GitHub Actions, GitLab, Buildkite and CircleCI the log keeps its
colour, task output included.NO_COLORorFORCE_COLOR=0turns it
off. - A warm run whose inputs have not moved reads every task key back from
one stored row, and a warm hit walks its outputs less. - The footer's
inforow names only the cache layers the run used:
local + remote cache,local cache,remote cacheorno cache. - Run rows put the machine a task ran on last, the tasks line counts
blocked tasks, a dim header names the columns, and every cache bar
segment has its count. - A stopped run keeps its finished tasks in history, and a stopped task
still names where it ran. - A Linux sandboxed task reports its CPU time and peak memory.
VX_AFFECTED_BASEnames the commit a bare--affectedcompares with.vx upgraderun from abunxcache suggestsbunx @vzn/vx@latest.vx whyon an uncached task says it saves no entry, so no
fingerprints name the input that moved.- A config migrated from Nx no longer carries Nx's task env, its
npm:
tags ornx-input:task names; input tasks areinputs:<name>. @vzn/vx-historykeeps each CI job's timings under its own
task set and carries every task forward, so a later job finds its
tasks' history.@vzn/vx-ciloads only its entry when not on CI.
Breaking changes
- Other runners' flag aliases on
vx runandvx watch(-t,-p,
-F,--skip-nx-cache,--dry-runand the rest) → removed. - The
vx run-many,affected,graph,query,ls,reset,
daemon,login,logout,linkandunlinkhints → removed. - Plugin API:
TaskOutcome.keyless→keylessBy, the id of the task
with no inputs the key folds.TaskExecutor.runningand
RunContext.whereRunningmay returnnull(the task waits for a
machine). - The local cache index and run history are rebuilt once (core schema
v46).
See also Upgrading.
How to update
vx upgrade