Skip to content
vxvx
GitHubBlueskydev.toRSS
Blog

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.

vx lock runs every config once and saves the result. Every run reads that file, so CI runs the config your reviewer saw. A TypeScript config can depend on where it runs:

exec: { command: `bun build src/index.ts --outdir dist --minify=${process.env.MINIFY ?? 'false'}` },

The file doesn’t change when MINIFY does. vx lock --check still catches it:

Terminal window
$ vx lock
✓ vx lock 4 project configs → .vx/projects.lock.json
$ vx lock --check
✓ vx lock --check up to date (4 projects)
$ MINIFY=true vx lock --check
✗ vx lock --check packages/d/vx.config.ts evaluates differently from the lock here — its config, a file it imports, or the environment it reads changed; run 'vx lock'
tasks.build.exec.command: "bun build src/index.ts --outdir dist --minify=false" → "bun build src/index.ts --outdir dist --minify=true"
$ echo $?
1

vx lock is the only command that runs a config. It also checks the graph rules. With no lock, a run stops:

Terminal window
$ vx run build --all
✗ vx run no lock: run `vx lock`

A project with no vx.config.*, whose tasks come from a plugin like turbo(), is locked too: vx lock records what the plugin maps, and a run reads that and maps nothing.

--check runs every config again and compares. It catches edits, and also an env value, a changed preset import or a computed command.

On this repo (12 projects) it takes 170 ms (min of 10, linux x64, 2026-10-23).

In CI:

Terminal window
vx lock --check && vx run ci --all

A pull request that changes a config without running vx lock fails the first command.

Not… Because
a cache key input vx lock must not change every key.
a substitute for inputs.env The lock saves config-time values. Run-time env goes in the key.

vx lock --check --format json gives the same exit code:

{"upToDate":false,"audited":4,"notAudited":0,"problems":["packages/d/vx.config.ts evaluates differently from the lock here — its config, a file it imports, or the environment it reads changed; run 'vx lock'\n tasks.build.exec.command: \"bun build src/index.ts --outdir dist --minify=false\" → \"bun build src/index.ts --outdir dist --minify=true\""]}

Same idea as --frozen-lockfile in package managers. Reference: vx lock. More at vznjs.github.io/vx.

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