A month of vlt CLI updates
Since 1.0, vlt has shipped install-speed wins, security hardening, and quality-of-life CLI fixes.

After our 1.0 release last month, the team has been shipping vlt CLI improvements at a steady pace. The client is at v1.0.10 and while this is not a complete changelog of everything we shipped since then, in case you want every patch note, you can find those in the GitHub releases. What follows is a hand-picked list of some of the favorite improvements on the eyes of the team.
Performance improvements
Warm installs got faster. Looking at our benchmarks, we have multiple scenarios that we use to measure different install scenarios (such as having a warm cache, or maybe an install that contains a lockfile, etc).
Previously, a cache-and-lockfile (one of these custom install scenarios we profile in our benchmarks) install of something like next sat around four seconds. Today that same fixture is closer to one second. The other fixtures there in our benchmarks follow the same pattern showing that warm-cache times are down by more than half.
A big chunk of that was a caused by a bug where nodes loaded from a lockfile were not marked as already resolved, so a warm vlt install still went back to the registry for tarballs sitting in the cache. On a Next.js project install that meant hundreds of GETs and tens of megabytes saved. Once the lockfile entries are trusted, the network work just disappears.
Extraction was another improved area, representing most of the time spent on a warm-cache instal, it stopped paying a libuv round trip per file, entries are now properly parallelized, and (in v1.0.9) registry tarballs now unpack straight off the cache file.
A few other worth noting highlights are:
- Peer-spec resolution used to compare the same semver ranges over and over. Those comparisons are properly memoized now, so resolving an install graph with no lockfile is no longer accidentally quadratic.
- The registry client does a zero-copy decode, parses JSON lazily, and the HTTP layer got a pass on undici options.
- Startup is leaner: enabled V8 compile cache at the bin entry, and starting lazy loading some of the internal helpers.
- Repeat installs that only touch optional subtrees no longer rewrite both lockfiles "just because."
- Workspaces with no dependencies keep their hidden lockfile instead of throwing it away and re-walking the tree.
- Peer nodes got stable, content-hashed IDs, so you stop seeing noisy lockfile churn for the same graph.
The public numbers move every day, so the best way to follow along is still the benchmarks dashboard. Clean installs were already in a good place at 1.0. The fun part since then has been making the "I already have this on disk" path feel as fast as it should.
Security improvements
vlt now caps gzip inflation the same way node-tar / npm do: min(2 GiB, compressedSize × 1000) thus avoiding tarball decompression bombs that would prevent an install from working properly.
A few security features have also improved:
- The security archive now treats packages from a configured registries.npm mirror as part of the npm ecosystem. If you install through the vlt.io npm mirror, selectors like :malware, :cve, and :severity actually get results again.
- :severity(<=critical) (and any other comparator against level 0) used to match every alerted node. critical is 0, and a falsy check bailed out of the filter. Comparators now mean what they say.
- :outdated works on authenticated, path-prefixed registries. The query used to drop the registry path and skip auth headers, so vlt query ':outdated' against something like https://registry.vlt.io/…/npm/ was a wall of 401s.
Improving the CLI
The rest of the month was a pile of "oh, that used to be annoying" fixes:
vlt setup is less confusing. As of v1.0.7, a single token covers both vlt.io registries from a same account. Unknown account names get a useful error. The --config option now also works, making it easy to persist the registry setup to either user-wide configuration or the project vlt.json file.
Project vlt.json wins. Project-level settings now properly override the user-level XDG file (deep merge, and null clears a user value). That is the precedence people already expected, and it makes checking in team defaults a lot less surprising.
Better query error messages. A typo like :difff or .dev used to look like an internal failure and ask for a full error log. Syntax problems now come back as EQUERY with a suggestion: "did you mean :diff?" / "did you mean :dev?".
Fixed vlt version missing options. Bumping a set of workspaces creates a commit and tag per package by default. Using --no-git-tag-version skips both (same flag as npm), so you can bump a bunch of workspaces and commit once. --no-commit leaves the bump uncommitted while still creating the tag.
Login and publish fail more gracefully. Multiple fixes to ensure logging in and plublishing packages are resilient experiences.
Plus a handful of correctness nits that show up the moment you leave the happy path. npm registry aliases are validated, using an undefined protocol prefix throw instead of being silently accepted on package specs, and packages listed in both dependencies and optionalDependencies are deduped.
Try it
If you installed vlt around v1.0.0 and have not updated since, you are missing out on a lot!
bash
curl -fsSL https://install.vlt.sh | bash