Your cache key is already in git's index
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.
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.
vx hashes your config after it runs, not the file’s text. So when a preset you import changes, every task using it gets a new key. This config spreads a preset:
import { defineProject } from '@vzn/vx/config'import { lib } from '../../vx-preset.ts'
export default defineProject({ tasks: { ...lib({ entry: 'src/index.ts' }), },})Change the preset, not the config:
$ sed -i 's/--outdir dist`/--outdir dist --minify`/' vx-preset.ts$ vx lock --check✗ vx lock --check packages/b/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" → "bun build src/index.ts --outdir dist --minify"
$ vx lock && vx run packages/b#build cache local 1↑
$ vx why packages/b#build verdict cache key changed: config what changed (1 component, 3 unchanged): changed config config 59020bd1597ff7c8 → 524418371d4d83bapackages/b/vx.config.ts didn’t change a byte. Its task did, so its
key did.
Demo workspace · vx 69617eb · 2026-10-23
flowchart LR P[vx-preset.ts] -->|import| C[vx.config.ts] PL[plugins] --> O C -->|vx lock runs it| O[the task object] O -->|"drop exec.remote, hash"| K[config part of the key]
The key hashes the task object, the thing the scheduler will run.
What a preset returned, what a template string became, what a plugin added: all of it is in the object, so all of it is in the key.
Part three of the key is
xxh3(JSON.stringify(hashableConfig(node.config))).
globalDependencies list to keep in
sync.turbo() builds tasks from turbo.json,
and those tasks get hashed. A plugin can’t change a task behind the
key’s back.exec.remote is dropped: it says where a task runs, not what it makes.
timeout, retries and description count, because a task given
longer may finish where a shorter one was killed.
vx lockA program can give a different answer on another machine. So no run runs
a config. vx lock runs them once and saves the objects. Every vx run
reads that file, so CI computes the same keys you did.
vx lock --check catches drift (more).
Turborepo’s globalDependencies and Nx’s namedInputs exist because
JSON can’t import. A vx.config.ts can, so a shared input list is a
constant you import.
That’s why vx has no named inputs, global inputs or global env, and won’t.
More at vznjs.github.io/vx.