
Worker types, one line per task
Distributed task execution sends a task to the machine type it names. Workers and servers run only while needed. Every task is one line; config refusals point at their line.
A run reads every project from the lock, turbo() and nx() mappings too. Distributed task execution ends at the critical path on less CPU and says what it held. The sandbox refuses a keyed file it would hide.

A run now reads every project, edge and mapped task from the lock, and nothing else.
Distributed task execution ends when the critical path does, holds less CPU, and says how much it held.
A sandboxed task can no longer pass while blind to a file its key holds.
This release is breaking. Read the breaking changes first.
In this release
vx lock froze each vx.config.ts, but a plugin’s project and
discover stages still ran on every run.
With turbo() or nx(), a run
read turbo.json, the manifests and git HEAD again, so what ran could
differ from what the lock held.
Now vx lock runs both stages and records what they return: each
project, its edges, and every task a plugin maps.
A run calls no project or
discover hook and reads only the lock. After you change a file a plugin reads,
run vx lock; vx lock --check names what moved.
$ vx lock --check✗ vx lock --check packages/app: the `project` stage maps it differently from the lock here — a file the plugin reads, or the environment, changed; run 'vx lock'
tasks.build.inputs.files[1]: "package.json" → "tsconfig.json" tasks.build.inputs.files[2]: (none) → "package.json"--affected selects a project whose locked config, name or edges
changed.
See vx lock.
A run with known task times could still end past its critical path, and a big worker stayed up for one small task while most of its cores sat idle.
Now a run ends by its critical path plus one warm-up. When a big worker’s big work is over, its last task moves to a smaller worker that joins just then.
Workers needed at once start as fewer, bigger ones, and a task goes only on a size with room for its cores and memory.
The footer weighs a run’s cost. ideal adds the least CPU a run ending
then holds; total adds the CPU the workers held, idle or not, warm-up
included.
$ VX_DTE_RUN=$(uuidgen) vx run build --all workers linux.small#1 stopped 3 done · warm-up 0.1s cpu peak 0.0 · used avg 0.0 cores · machine avg 0.1 · booked avg 0.3 cpu utilization machine 5% · tasks 0% · worker 2% ideal 3.2s: each task as long as it took, placed perfectly, holding 0.3 core-s at least best 1 × linux.small (2c/8G linux-x64): 3.2s, the fewest of its sizes any 1 × (2c/8G linux-x64): 3.2s, of any cores, the least core time total cpu utilization machine 5% · tasks 0% · worker 2% · held 6.4 core-sWhen a run ends late, a drift line says why, along the chain that
ended it: workers joining, waiting for room, sending inputs, and time
after the last task.
See Distributed task execution.
A sandboxed task that keyed tsconfig.base.json at the workspace root,
with no read grant for it, ran blind to it.
A tool that stats a config before it opens it left no violation, so the task passed and its wrong output was cached.
Now vx checks the key’s files outside the project against the sandbox’s grants before the command starts. A file no grant reaches fails the task, unrun, with the grant to add.
$ vx run build --filter packages/lib┌─ packages/lib#build > failed (exit 1, 1 sandbox violation)
$ mkdir -p dist && cat ../../tsconfig.base.json > dist/tsconfig.json
├─ SANDBOX ───────────────────────────────────────────────────
vx: hidden by the sandbox, held by the key: /home/user/acme/tsconfig.base.jsonvx: the sandbox hides a file this task's key holds, so it was not run: /home/user/acme/tsconfig.base.json. Grant it, e.g. `allow: { read: ['../../tsconfig.base.json'] }`.
fix: exec.sandbox.allow: { read: ['../../tsconfig.base.json'] }
└─ packages/lib#build ── (0ms) failed (exit 1, 1 sandbox violation)See Sandboxing.
vx lock refuses a glob no file can match: a backwards class ([z-a]), surrounding whitespace, a control character, or braces expanding past 1024.cache.db page or a FIFO in an output tree degrades to a miss; a long Ctrl-Z no longer lifts a task’s timeout.vx last --live --follow exits 1 when its run is lost or killed..vx/dependencies.lock.json written by a newer vx is refused.dte() machine names may hold a dot again (c5.large).turbo() keys package.json past negated inputs; nx() reads ?? outputs and refuses kebab-case or alias output options it cannot map..gitattributes files and lockfile plugins.vx upgradeRun vx lock after upgrading. Every cached task misses once.
.vx/projects.lock.json → version 6, holding the project and discover stages. A plugin’s change to projects, edges or mapped tasks reaches runs only through vx lock.vx-cache-v55: the local cache starts empty once.See also Upgrading.