vx hashes your inputs without reading them. Git already stored a hash
for every tracked file.
This repository, 48 MB · linux x64, 4 cores, warm page cache · 2026-10-23
A key has eleven parts
A key is an xxh3 hash, seed-chained across eleven parts.
Each part goes in under its own label (task:, config:, upstream:,
plugin:, inputs:, …), with lengths and \0 separators so two
layouts never collide.
The parts, as Caching numbers them:
- The key-derivation sentinel (
CACHE_VERSION), so a change to how keys are derived is never served by an older entry. - The task id,
project#task. - The evaluated task config, not its source text (its own post).
- Arguments forwarded after
--. - The values of the env vars in
inputs.env. - The output of each
inputs.runtimecommand (node --version, say). - The same for
workspaceRuntime, run once per run. - The keys of the upstream tasks this task depends on (cascade post).
- What a plugin's
keystage adds, folded right after the upstream keys and before the input files.@vzn/vx-lockfilefolds each project's dependency closure here. - The platform, only for a task that names
exec.platforms. - The content hashes of every file
inputs.filesmatches: its git blob id, plus its mode for an executable or a symlink, and for a symlink what it points to.
Part 11 is where the money is: hundreds of files per package, hundreds
of packages.
vx asks git once
One git ls-files -s -v reads every path and its blob id from the
index. One git status --porcelain -uall beside it lists dirty and
untracked files.
An index id is trusted only when it matches the disk, so four prunes run
against it:
- a dirty path;
- a
skip-worktreeorassume-unchangedpath; - a path a clean filter could rewrite;
- an id whose blob is not the size the index recorded for the file.
A repo whose config weakens git's stat check (core.trustctime=false,
core.checkStat=minimal) trusts no index id at all. Pruned files are
hashed the way git would hash them.
Finding clean filters costs one git var -l and a stat per attributes
file.
A commit never changes a key
$ vx run build --all # after editing packages/a
⏺︎ 10ms success miss packages/a#build
⏺︎ 7ms success miss packages/c#build
local 2✓ 2↑
$ git commit -am edit && vx run build --all
local 4✓
Demo workspace · vx dbe4033 · 2026-10-23
A dirty file gets the blob id git gives it on commit. So after the
commit, everything hits.
What the key leaves out
| Not in the key | Why |
|---|---|
exec.env.passThrough |
A HOME or CI that differs per machine would split a shared cache. |
| Undeclared tool versions | Add node --version under inputs.runtime when it matters. |
exec.remote |
Where a task ran says nothing about what it made. |
.vx/projects.lock.json |
vx lock itself must not re-key the world. |
| Ignored files |
src/** skips them. Name one outright (.env.local) to hash it. |
A variable that changes the output goes under inputs.env. A shared
root file goes under inputs.workspaceFiles. When a key changes, vx names the part.
why
More at vznjs.github.io/vx.
Originally published on the vx blog. vx is open-source (MIT) task orchestration with a build cache: vznjs.github.io/vx · GitHub.
Written with AI assistance.
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more