Updates & rollback
The contract: stable updates must not silently swap a major workflow component, and a bad lockfile or config migration must never be a one-way trip. Blak enforces this with three commands, rollback snapshots, and explicit upgrade migrations.
The commands
Section titled “The commands”:BlakUpdate
Section titled “:BlakUpdate”- Checks that
package.channelhas not changed since the last accepted update or upgrade. - Checks for pending breaking migrations.
- Snapshots your lockfile,
lua/blak/user.lua,extras.json, migration state, and update state tostdpath('state')/blak/rollbacks/. - Runs
:Lazy update.
The snapshot happens before the update, so if :Lazy update itself fails, you still have the original lockfile and config state.
:BlakRollback
Section titled “:BlakRollback”- Finds the most recent rollback snapshot.
- Restores the lockfile and config state that existed before the update or upgrade.
- Reloads Blak config and runs
:Lazy restore.
The lockfile pins and tracked config state are restored. Lazy then checks out the pinned plugin commits. Network access may be needed for missing repositories, commits, or build artifacts; external tools and plugin-managed data are not included.
Pins and your lockfile
Section titled “Pins and your lockfile”The checkout’s lazy-lock.json holds Blak’s shipped pins: a tested commit for
every plugin that core or any extra can install. Blak never writes to it.
Your own lockfile lives at stdpath('state')/blak/lazy-lock.json. On startup,
and before :BlakExtras sync installs anything, Blak copies in the shipped pin
for every plugin your lockfile does not list yet. A plugin is therefore first
installed at the commit Blak was tested with, whether it comes from core or
from an extra you enable later. Entries already in your lockfile, from
:BlakUpdate, :Lazy update, or a rollback, always win.
Because updates write only to the state directory, the checkout stays clean
and git pull --ff-only keeps working. editor.mini modules are the one
exception to pinning: you choose them, so they install from their default
branch.
The convention
Section titled “The convention”Stable updates are conservative. They do not:
- Swap your default picker.
- Swap your completion engine.
- Swap your LSP wiring strategy.
- Change
<leader>or<localleader>.
Changes to those defaults require an explicit migration and release note; optional alternatives live in extras. A package.channel change is also treated as an upgrade-only move. See package.channel in the schema.
For intentional bigger moves there’s a separate command:
:BlakUpgrade
Section titled “:BlakUpgrade”For things like switching channels (stable → edge), major-version bumps, or migrations that are allowed to swap workflow components. :BlakUpgrade snapshots first, applies pending migrations, records the current channel as accepted, then runs :Lazy update. The split exists so that you can run :BlakUpdate without thinking, and run :BlakUpgrade deliberately.
A typical update flow
Section titled “A typical update flow”:BlakUpdate " snapshot + channel-safe update" something feels off ↓:BlakDoctor " confirm what broke:BlakRollback " restore the previous setIf you want the update but a single plugin regressed:
:BlakRollback " restore everything:Lazy update foo.nvim " update just the oneSnapshot retention
Section titled “Snapshot retention”Snapshots accumulate in stdpath('state')/blak/rollbacks/. Blak doesn’t auto-prune them because they are cheap and the safety they provide is high. If they bother you, deleting old ones by hand is fine; :BlakRollback always uses the newest. Legacy lockfile-only backups in stdpath('state')/blak/lockbacks/ are still readable.
On nightly Neovim
Section titled “On nightly Neovim”Supported stable Neovim releases are the baseline. CI requires the stable smoke and installer checks and runs an advisory nightly smoke job. Nightly remains a compatibility target; upstream API changes can still cause errors. If one does:
- Upgrade Neovim.
- Update the Blak checkout as described below, then run
:BlakUpdatefor plugin fixes. - Use
:BlakRollbackfor a plugin regression and report the issue. For a Neovim regression, return to a supported stable Neovim release; Blak’s snapshots do not contain the editor executable.
Updating Blak itself
Section titled “Updating Blak itself”:BlakUpdate and :BlakUpgrade update plugins; they do not fetch this distribution’s Git checkout. Review NEWS.md, preserve your local changes, then update the checkout explicitly:
git -C ~/.config/blak pull --ff-onlyUse your actual XDG config path if different. If Git reports local changes or divergent history, resolve those before proceeding. Restart Blak and run :BlakUpgrade for pending migrations. Rollback snapshots do not include the distribution’s Git revision.
Upgrading from v0.3 or earlier: your lockfile used to be the checkout’s
lazy-lock.json, so git pull --ff-only may refuse because of local changes
to it. Discard them with git -C ~/.config/blak checkout lazy-lock.json, then
pull. On the next start Blak records the commits you have installed in the new
state lockfile, so no plugin moves.
Checkout changes take effect on the next startup; :BlakUpgrade is not a gate
on loading manually updated source. For the native TypeScript transition,
review the release note before updating. If you want to retain ts_ls, replace
lang.typescript or lang.typescript-tsgo with lang.typescript-legacy as part
of that checkout upgrade, before restarting. Otherwise the standard extra uses
tsc; remove any explicit lsp.servers.ts_ls entry to avoid starting both.
Run :BlakToolsInstall, wait for completion, and restart after upgrading.
package.channel currently selects Blink’s version policy: stable uses 1.*; edge and nightly both build the development branch with Cargo. Other plugins follow their configured branches. These are not separate Blak release branches, and upstream plugin updates can still introduce breaking changes. Direct :Lazy commands bypass Blak’s channel/migration checks.