
Fewer surprises when you switch
vx init maps a root package.json's repo-wide scripts, a bare --affected takes its base from affectedBase, and Turbo and Nx commands, flags and cache switches now land where you expect.
turbo() now runs root tasks, single-package repos and with sidecars as Turbo does, nx() finds every project in an integrated repo, and the CLI starts 2× faster.

vx 0.0.200 to 0.0.242 shipped on 2026-09-29 and 09-30. Most of the batch
makes turbo() and nx() from @vzn/vx-migrate run more real repos
unchanged. Core also starts faster, saves misses faster, and tells you how
to fix a task that rewrites its own input.
In this release
A //#format task in turbo.json used to need a vx.config.ts at the
root, or vx run format --all said no project declares it. Core gained a
discover stage, where a plugin names directories that become projects,
and turbo() names the root when turbo.json declares a //# task. A
single-package Turbo setup, with no workspaces at all, now maps its
plain tasks to the root too. The two-line workspace file is all you need:
import type { WorkspaceConfig } from '@vzn/vx/config'import { turbo } from '@vzn/vx-migrate'
export default { plugins: [turbo()] } satisfies WorkspaceConfigPRs #1859, #1867. Deep dive: From Turborepo.
Turbo’s with key (web#dev with api#dev) was a todo, so starting
web#dev started no API. Each persistent sidecar is now an edge: vx
starts it and runs the task beside it. A task with no script of its own,
like the dev in Turbo’s with-tailwind example, becomes a group that
starts its sidecars.
vx run web#devPRs #1864, #1866. See Turbo and Nx config support.
Under turbo(), a * name in env or passThroughEnv (NEXT_PUBLIC_*)
now expands over the run’s environment, as Turbo does, instead of leaving
the task without those variables. Turbo 2’s framework inference applies
too, so a Next.js build gets its NEXT_PUBLIC_* values and no longer
inlines empty ones. A !NAME entry removes that name from the list.
NEXT_PUBLIC_API_URL=https://api.example.com vx run build --allAn integrated Nx repo keeps its project.json libraries out of the
package manager’s workspace list. nx() attached only the ones that list
named: 1 of analogjs’s 21 build tasks. It now names each graph node’s
directory through the discover stage, so every project runs, and a root
project needs no hand-written config. A warm nx() run also reuses core’s
git status for its graph key instead of spawning its own.
vx run build --allvx --version spent most of its time importing the run path. Every verb
now loads on use, run included, so --version went from about 60 ms to
25 ms. A warm no-op run is unchanged.
vx --versionPR #1883.
Each miss paid async round trips to check where its outputs live and to
clean them before the task ran. Those now run synchronously for small
sets. A full-miss run of 100 tasks went from 594 ms to 541 ms, and from
979 ms to 898 ms at --concurrency 1.
vx run build --allEvery cache call spent 2.1 s backing off when the REAPI server refused the connection, so a five-task run took 24 s. Once one call has spent its retries, the next ones give up at the first refusal until the server answers again. The same run now takes 2.6 s and falls back to the local cache.
vx run build --allPR #1887.
A task that rewrites one of its own inputs, like TanStack Router’s
committed routeTree.gen.ts, is never saved: its key no longer describes
the files. The status line named the file but not the fix. It now says to
declare the file in cache.outputs:
[vx] app#build: `packages/app/src/routeTree.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.outputsPR #1877.
allow.gitConfig was ignored, so .git/config stayed read-only to every
sandboxed task. It now reaches the sandbox, for that task only. A dir/
write grant outside the project is created when missing, so a tool can
fill a cold cache in your home. When a task fails, vx names the writes the
sandbox refused outside the project and the directory to grant.
import { defineProject } from '@vzn/vx/config'
export default defineProject({ tasks: { compile: { exec: { command: 'bun build --compile --target=bun-linux-x64 src/bin.ts', sandbox: { allow: { network: ['registry.npmjs.org'], write: ['~/.bun/install/cache/', '.*.tmp/**'], }, }, }, }, },})PRs #1851, #1875. See What you can grant.
vx why --run and vx last accept the short id that vx last --list
prints. The MCP tool whyDidThisRerun answered “not found” for it. It now
resolves ids the same way the CLI does.
ok 2026-10-08T19:56:51.484Z 01a11d17-099c 1 task · 0 hits 148ms $ vx run build --allPR #1888. Deep dive: Agents and MCP.
PLUGIN_HOOKS gains discover, after config
(#1859).whyDidThisRerun’s runId now takes a prefix
(#1888).See Upgrading.
npm install -D @vzn/vx@latestA standalone binary updates itself with vx upgrade.