Skip to content
vxvx
GitHubBlueskydev.toRSS

// the vx blog

Blog

Stories, features and the design behind vx.

RSS

A sandboxed task reads what it depends on, and nothing beside it

A sandboxed task can read the outputs of the tasks it depends on, in any project, and never their sources. With hermetic: true it reads nothing of the machine you did not name.

One test suite, six task runners, the same 993 cases

993 task-runner cases, compiled from the test suites of Nx, Turborepo, Vite Task, moon, Nix and vx, run sandboxed against all six. Every result is on one page.

A DTE relay that stores nothing

vx DTE's relay keeps no message, no timer and no storage row. It passes frames between sockets, and the two ends make sure each one lands.

$12k a year saved with vx vs Nx Cloud

vx's CI is one run spread over its own Linux and Mac runners. On Nx Cloud's Team plan with Nx's agents, the same CI would cost about $12,192 a year. On vx it costs $0.

When running beats downloading

If a download would take longer than running the task, vx runs the task. 500 remote hits finish in 1.15 s instead of 3.12 s.

Every task has a key

A task you never cache still has a key. Change its inputs and every cached task after it runs again, so you never get a build made for old inputs.

What a run does not do

A run does no cleanup at exit, never walks the shared store, and shows no "time saved" it did not measure.

Brackets in a glob mean brackets

In a vx glob, [jt] matches one character, as in Turbo, Nx and your shell. A Next.js route folder like app/[id] needs escaping.

Cache transfers wait on the network

Cache transfers mostly wait on the network, not a core. vx runs up to 64 at once, so 1,000 uploads to a cache 30 ms away take 2.5 s.

A cache hit is a file copy

A local cache hit is a file copy the kernel does. 1,000 hits of 96 KiB restore in 411 ms, and vx never hard-links.

Branch caches a server enforces

A pull request reads what main built and can never write where main reads. The CI token decides writes, one header steers reads. How it works, and how a cache server builds its half.

vx on real repos: wall times

Seven open-source monorepos that already run on Turborepo or Nx, timed under their own runner and under vx: cold, cache restore, one-file edit and a clean clone. Raw seconds, min of three, slower rows included.

Say which projects may depend on which

Tag your projects by layer and say which may depend on which. vx lock refuses a dependency that breaks a rule, and runs pay nothing.

One name per setting

Every vx setting has one name. run.retries is VX_RUN_RETRIES and --retries. Learn one, know all three.

Where a task run spends its time

We traced one workspace through vx, Turborepo 2.11.8 and Nx 23.3.0, cold and warm. A cold run is the tasks. A warm run is how little the tool does.

An input changes mid-run: what four task tools do

A file changes while a task runs. Does the cache save the output anyway? We read the source of vx, Turborepo, Nx and Vite Task.

A glob stops at the next project

A project's globs stop at the next project's folder. A change elsewhere reaches a task only through a declared dependency.

A cache that stays the size you chose

vx cache clean keeps the cache at the size you pick, by age and by last use. It refuses a bound of zero.

Carry the scheduler's memory between CI runners

A fresh CI runner has no run history, so it can't know which tasks are slow. One JSON file carries the learned times from runner to runner.

A cold run asks the cache nothing

With an empty cache, vx skips the lookups that would all miss. On 1,000 projects the first task starts 136 ms after vx run.

Which task runs first?

When more tasks are ready than workers, a runner has to pick. Five rules for picking, the graphs where each one wins and loses, measured with vx, and why recorded timings beat every rule that has to guess.

How many tasks at once? Count the cores you are given

In a container, the CPU count a program sees is the host's. vx reads the limit the kernel enforces, so it never starts more tasks than you have cores.

A key with nothing to run

A task can have inputs and no command. Tests that depend on it run again when those files change, and nothing extra spawns.

The reference at your version

vx docs <query> searches the docs for the vx version you run, so the answer matches your binary.

From npm scripts or Vite Task

One command turns package.json scripts or Vite Task config into vx tasks. npm pre and post hooks keep their order.

Guard rails that tell you the fix

When vx stops something, it names the file and the fix. Sandbox refusals, two tasks writing one folder, a config that hangs.

A scheduler that learns from your runs

@vzn/vx-history starts the longest chain first and keeps memory-hungry tasks apart, using times and memory from your past runs.

The basics, done carefully

vx run picks the project you are in, never waits on a picker in a pipe, and gives each task its own fold on GitHub Actions.

An upgrade you can trust

vx upgrade checks npm’s registry signature and the SHA-512 before it replaces anything, upgrades an npm, pnpm, bun or yarn install through that package manager, and swaps back if the new binary does not start.

Extend vx in an afternoon

One command scaffolds a working plugin and its test. Bring your own remote cache through one small interface, hook setup and teardown around the run, or drive vx from your own scripts with run() and planRun().

What goes into a key, and what comes back

Files, env vars, tool versions and upstream tasks go into the key. Outputs and logs come back. A damaged download is a miss, never a wrong build.

See inside a run

Why did this task run? What did the run do? Where did the time go? One command answers each, in text or JSON.

One failure, and exactly what it takes down

A broken build skips only what depends on it, and each skip names the failure. One flag picks fail-fast or run-everything.

When a task misbehaves

A task hangs, gets killed, prompts for input, or calls vx run on itself. vx tells you what happened in one line.

Say exactly which tasks to run

dependsOn says what a task needs first, --filter says which projects to run. A name that matches nothing stops the run before it starts.

A remote server you can trust in production

Connect @vzn/vx-reapi to BuildBuddy or your own server. Repeat runs skip the worker, every byte is checked, and a stuck server is a miss, not a hang.

Watch a CI run while it runs

@vzn/vx-otel sends each task's span the moment it ends, so your dashboard follows the build live, for the same CPU as one send at the end.

A task sees only the env it names

Your shell holds dozens of variables, some of them secrets. A task gets a short fixed list plus the names you write down, and secrets print as ***.

Output that fits the run

One task shows all its output. Many tasks show one line each for what ran. CI shows everything. You can always pick.

The cache on your terms

Turn the cache off, refresh it, or read one layer and write another. Every clone shares one store, and a pull request never writes what main reads.

Built for the agent at the keyboard

vx gives coding agents JSON with a schema for every answer, keeps a failure's log and files, and stores run history in a SQLite file.

One command to know your workspace

vx info answers the first questions of every bug report on one screen: versions, workers, memory, cache size, flaky tasks, sandbox.

A refusal an agent can read

When vx refuses, it prints a stable code and a link to the fix. Scripts and agents check the code, not the wording.

The small things

Mistype a flag or a task and vx prints the fix. Forget the task and you get a picker. Tab completes the rest.

Your run, on GitHub

Add one plugin line and every vx run shows up on GitHub: a job summary with failures first.

A run you can read

One line per task while it runs, the full log only for what failed, and one summary block at the end. Nothing prints below it.

The last run, on request

The terminal scrolled away? vx last --failed shows the run that broke and the command that runs only what failed.

See what a task really is

vx show prints a task the way the run sees it, after presets and plugins changed it. No guessing what your TypeScript config turned into.

Run only what a change reaches

vx run test --affected runs what your branch changed and everything downstream of it, and says why each task was picked.

See the plan before you run it

vx run --dry shows which tasks would come from the cache and which would run, with keys and expected times. Nothing runs.

What vx is, and what it refuses to be

vx runs and caches your task graph, correctly, and nothing else. How a run works, and what will never be inside.

Why vx is fast: five decisions, not a trick

What vx adds to a run with nothing changed, with no daemon, and the five design choices behind that number.

There was no choice on the market

Turborepo runs and caches. Nx adds a platform. vx is the runner with open plugin points, and nothing behind a paid cloud.

Performance, modularity, extensibility. In that order.

Three priorities in a fixed order settle every design choice in vx: performance, then modularity, then extensibility. Nine rules follow.

Your cache key is already in git's index

vx reads file hashes from git's index instead of reading your files: 2.3 ms for 2,527 files. A commit never changes a key.

Hash what the config turns into

vx hashes your config after it runs. Change a preset you import and every task that uses it gets a new key, with no list to maintain.

After a cache hit, dist holds only what was cached

vx clears a task’s declared outputs before it runs and after a hit, so a deleted file never lingers in dist and gets shipped.

Every cache key is known before anything runs

A task's key includes its dependencies' input keys, never their outputs. So every key in the graph exists before the first task starts.

No daemon, and still quick when nothing changed

vx has no background process watching your files. A run with nothing to do stays quick, and it can never act on a stale view of the tree.

Task priority in milliseconds, not 8.5 seconds

On a 3,270-task graph, task priority took 8.5 seconds with set unions. Bitsets do it in single-digit milliseconds, and cache restores never take a slot from a build.

The sandbox catches the input you forgot to declare

A cache is only correct if the declared inputs are every file the task reads. Instead of guessing that set, vx runs a task with the declared paths as the only workspace files it can touch, and fails the run on an undeclared one of its own.

vx why names the change that made a task run again

You changed one file and got twelve rebuilds. `vx why` names the part of the cache key that changed, and follows it to the root cause.

A lockfile bump rebuilds only what it reaches

Core keys no lockfile, and folding the whole file into every key would make one bump rebuild everything. @vzn/vx-lockfile keys each task on its own project's dependencies, so an astro bump here rebuilds 8 of 151 tasks.

vx lock: run configs once, check them in CI

vx lock runs every config once and saves the result. Every run reads it, and vx lock --check catches drift a file hash can't see.

One command per task; the shell is the API

A vx task is one shell command. That one rule is why remote runs, the sandbox and cache replay all work without special cases.

A plugin fills one stage, often in under 25 lines

vx is a pipeline with a named hook at every stage. A plugin fills only the stage it needs, and most example plugins are under 25 lines.

Running here is not a plugin

Your machine is always the last place a task can run, and the local cache is always asked first. A dead remote never breaks a build.

Observability that cannot break a run

A broken telemetry plugin costs a warning, never a red build. A sink that throws is turned off for the run, and one that hangs is cut off after 3 seconds.

Run tasks on a remote worker pool, keep the scheduler local

@vzn/vx-reapi runs tasks on any Bazel Remote Execution API worker pool. The graph, the placement decision, retries, timeouts and the cache all stay on your machine; what moves is one self-contained action per task.

Give your coding agent the build's memory

Your coding agent asks what you ask: what can I run, why did that re-run, which tasks flake. vx answers each as JSON from the CLI.

Ctrl-C leaves nothing running

Press Ctrl-C and every process vx started is gone within two seconds. Every way a run can end goes through the same teardown.

Watch: a content gate, not an event storm

vx watch lets the cache key decide what runs. A README edit runs nothing, a source edit runs exactly what it reaches.

Flaky is a claim only declared inputs can back

vx calls a task flaky only when the same inputs both passed and failed. Your local run history is enough, no service needed.

From Turborepo to vx in one command

One command turns a Turborepo workspace into native vx config. Same dependsOn syntax, same --filter, and nothing dropped silently.

From Nx to vx: keep the graph, drop the daemon

One command writes native vx config from the project graph your installed Nx resolves, plugin-inferred targets included.

Benchmarks you can re-run

One synthetic task graph, each tool in its own native config, every number one command away.

Hello, vx

vx runs your monorepo's tasks and caches them, on Bun, with nothing behind a paid cloud. Here it is in one picture and one run.

Turborepo, Nx and other product names are trademarks of their owners. vx is not affiliated with or endorsed by them.