Skip to main content
How to upgrade bd and keep your projects in sync.

Checking for Updates

Short Version

  1. With your current bd, sync remote-backed databases before installing the new binary: bd dolt push bd dolt pull
  2. Back up with that same current binary, before installing: bd export --all -o .beads/backup/pre-migrate-$(date +%Y%m%d).jsonl
  3. Upgrade using the command that matches your install method.
  4. After upgrading: bd upgrade review bd hooks install bd version
  5. If crossing a schema migration on a remote-backed database, only the designated migrator runs: bd migrate schema bd dolt push
Other clones must not migrate independently. After the migrator pushes, each other clone runs bd dolt pull with its current binary, then installs the new one. A clone that already has the new binary adopts the migrated database by moving its local database aside and running bd bootstrap; bd bootstrap on its own leaves an existing database untouched. The full procedure is below.

Upgrading

Use the command that matches your install method.

Quick install script (macOS/Linux/FreeBSD)

PowerShell installer (Windows)

Homebrew

If you still have the old tap formula installed as bd, switch to the Homebrew core formula:

go install

From Source

After Upgrading

Important: After upgrading, update your hooks:
Why update hooks? Git hooks are versioned with bd. Outdated hooks may miss export refresh, legacy fallback, or safety fixes.

Database Migrations

After major upgrades, check for database migrations:

Upgrading to 1.3.0

A 1.2.2 or 1.1.x database sits at schema v53, and 1.3.0 knows v66. That is more than a routine upgrade: 1.2.2 was a recovery release that shipped the 1.1 code under a higher version number, so there is a release line’s worth of schema between the two.

Back up first, with the binary you have now

Do this before you install 1.3.0. Under the new binary, bd export triggers the migration before it exports, so a snapshot taken afterwards is a post-migration snapshot and cannot protect you against the migration going wrong. The same ordering rule as the section below applies: do all syncing with your current binary, since once 1.3.0 is installed the pending-migration gate refuses bd dolt push and bd dolt pull too.
For a Dolt-native snapshot that keeps history and config, configure a destination and sync it. Bare bd backup takes no backup — it is a command group that prints help and exits 0:

What the migration looks like

On an embedded or local store, the first command you run after installing applies the whole set, in place. (A shared dolt sql-server is never auto-migrated — see Shared servers below.) The main series runs 0054 → 0066 (13 migrations), and then the clone-local series runs through the same printer with its own numbering, 0012 → 0026 — so it is about 28 migrations, and the counter visibly restarts partway through:
A counter jumping backwards looks exactly like a loop. It is not one — let it finish. Expect the run to take noticeably longer than the commands after it (two passes rewrite rows rather than reshaping tables). It is crash-resumable and picks up where it left off. Those lines go to stderr, and only when stderr is a terminal, so a piped or CI upgrade prints nothing at all. Silence there is not a stall either.

Repair blocked flags after migrating

Migration 0059 can mark issues as blocked when they are not, which drops them out of bd ready without any error (#7037). It happens on a Dolt server older than 2.4.0, and the embedded engine is likely affected too. Once a database has migrated, run this once for that database on every clone:
It recomputes the flag from the dependency graph, so it is safe on any database, and "rows_corrected" greater than zero means the database was hit. It works in embedded, server and proxied-server mode, so run it even where bd doctor cannot check blocked state.

Upgrade every client that shares a store, together

A bd binary refuses a database migrated past the schema it knows, rather than proceeding blind. One machine upgrading takes the shared store forward and every client still on 1.2.2 stops working. Check for a second binary earlier in your PATH with which -a bd, and restart any long-running bd serve — noting that bd --readonly serve is now refused outright, so a server scripted with that flag will not come back up until you drop it. If the store is a shared dolt sql-server rather than a local one, follow Shared servers below: 1.3.0 will not migrate it without explicit consent, precisely so the fleet upgrade can come first. If you need to go back, the rollback is a schema-cursor rollback rather than a downgrade of the data — see Accidental v1.2.1 Release, whose procedure is the same for any cursor rollback even though its worked example is that release. Once you are on the new binary, bd upgrade review prints exactly the changes between the version you were running and this one. Several commands changed defaults, so read it before your first session.

Remote-backed databases and multiple clones

bd refuses to silently apply pending schema migrations to a database that has a Dolt remote configured. Migrating more than one clone of a shared remote independently forks the schema, after which bd dolt pull can no longer merge — the break is silent and, across a primary-key-reshaping migration, unrecoverable (#4259). The supported flow is: one machine migrates and publishes; every other clone takes the migrated database from the remote instead of migrating its own. This applies to every upgrade that crosses a pending migration on a remote-backed database — the same procedure whether you are moving to a prerelease or to a stable release. The gate is state-aware by default (#4516): before blocking, bd consults the remote’s cached schema state and
  • auto-migrates when the remote is at the same schema version as this clone — no one has migrated yet, so this clone is a safe first-mover (concurrent first-movers converge to identical tables). It reminds you to bd dolt push afterwards. This applies to embedded mode only: a shared server always stops for consent, because migrating it changes the schema every connected client sees (see Shared servers below).
  • stops and directs you to adopt (bd bootstrap) when the remote has already been migrated by another clone. On a clone that already has a local database, bd bootstrap by itself reports Nothing to do. and changes nothing; adopt with the steps under Multiple clones sharing one remote below.
  • stops for a human decision when this clone and the remote applied different content for the same migration (a genuine fork), or when the remote’s schema state cannot be read from the cached ref.
Set BD_SMART_GATE=0 to opt out and make the gate block unconditionally. The recipes below are the explicit path and work the same in either mode. Important ordering: once the new binary is installed, a database with pending migrations is gated on every open — bd dolt push and bd dolt pull are refused too, not just bd migrate. So do all syncing with your current binary, before you install the new one. There is one exception, and the gate names it when it applies: if the stop is the data-behind one — this clone is level with the remote on schema but is missing commits it has not pulled — then bd dolt pull is the remedy and is allowed through, because migrating before that pull is what wedges the clone. A proxied-server workspace refuses bd dolt pull outright, so there the pull runs on the server host instead. See Clone behind the remote. Every other pending-migration refusal still blocks the pull, so the ordering rule above is what to plan for. Back up before you migrate. Schema migrations assume the database matches the shape the previous migrations left behind; real databases sometimes drift (interrupted writes, tooling bugs, very old bootstraps). A JSONL export is cheap, issue-complete, and importable by any bd version:
bd export captures issues, not Dolt history or config — for a full snapshot also copy the .beads directory (or dolt backup in server mode) while no bd command is running. Single clone (including a solo user with a remote):
If bd’s remote-migrate gate blocks the run, it prints the available options — migrating here as the designated migrator, adopting the remote’s already-migrated database, or recovering a fork — and asks for an explicit operator decision. Follow the guidance it prints. For scripted or CI upgrades where nobody reads the output, run bd migrate schema as an explicit step in exactly one job, never in all of them. If the gate blocks a run it prints both the available options and the scripted override that fits the situation. Multiple clones sharing one remote:
The pull in step 3 brings the migrated schema down with the data, so the new binary finds nothing to migrate and the clone keeps its local state. Between that pull and the install, the old binary refuses the database (it is now newer than the schema the old binary knows), so install straight away. A clone that already runs the new binary cannot take step 3 as written: the gate refuses bd dolt pull while the clone has pending migrations. Adopt the migrated database by re-cloning it. bd bootstrap never replaces an existing database — run against one, it prints Database already exists and Nothing to do. and leaves the clone on the old schema — so move the local database aside first, then re-apply the step 1 backup:
The re-clone brings only what the remote has, so the import restores this clone’s clone-local beads (wisps, for example) and anything step 1 did not push; rows the remote already holds in a newer version are skipped as stale. The backup has to come from the old binary, as in step 1: on a clone with pending migrations the new binary’s bd export fails. Keep the moved-aside directory until you have checked the result — the old binary can still open it. In server mode the database lives under .beads/dolt/ instead; stop the server and move the database outside .beads/dolt/ (see re-clone-gotchas). If a clone was instead migrated independently and bd dolt pull later fails with cannot merge because table dependencies has different primary keys in its common ancestor, the schema has already forked — follow the recovery playbook: the pk-fork-refused runbook.
bd doctor includes a migration-content-skew check that flags a forked schema against the cached remote ref — a useful post-upgrade verification. It runs in both server and embedded modes.

Clone behind the remote

This is the one pending-migration stop whose remedy is a bd dolt pull, and the one the gate links here rather than to the migrate-or-adopt recipe above. A proxied-server workspace refuses that pull, so it runs on the server host instead; the last part of this section covers that case. It fires when the clone is level with the remote on schema but behind it in data — the remote has commits this clone has not pulled. On schema alone the clone looks like a safe first-mover, so before #6575 the gate let it migrate; the new schema commits then landed on a history missing those commits, and once a migration moves a tracked table onto Dolt’s ignore plane every later bd dolt pull refuses to merge (#6368). The clone can no longer fetch what it was already behind on. The remedy is the pull, and nothing else:
Nothing local is discarded: with no commits of its own the pull is a pure fast-forward, and if the clone has diverged the pull merges (resolve any conflicts, or decide them in bulk with bd dolt pull --strategy ours|theirs on embedded storage / bd conflicts resolve on a server store). Once there is nothing left to pull, the clone is the first-mover it looked like and the migration proceeds on its own. Do not force past this stop. bd migrate --force applies the migration while the clone is still behind — precisely the state the stop exists to prevent — and the bd dolt push that would follow is rejected as a non-fast-forward anyway. On a shared server, the pull is still the first step, but the migration after it needs explicit consent, because it promotes the schema for every co-resident client at once (see Shared servers). Confirm those clients are upgraded, then:
--force is the consent form here because this stop always has a remote configured — the behind-ness is read from the remote-tracking ref — and the bare bd migrate schema consent applies only to a shared database with no remote. It is safe at this point and only at this point: the pull has already landed the commits the clone was missing. In proxied-server mode the database is shared too, so the same two steps apply, but they run from two places. The workspace refuses bd dolt pull (proxy.dolt_pull.unsupported) and bd conflicts, so the pull, and any conflict resolution it needs, runs where the database lives: on the server host, from a workspace with direct database access. The consent step can then be run from the proxied workspace, but only once that pull has landed:
Nothing in the proxied workspace enforces that order. It refuses the bare bd migrate verb but not bd migrate schema --force, and BD_ALLOW_REMOTE_MIGRATE=1 grants the same consent to any command. Both are honored before this stop is reached, so the stop never sees them: run either one before the pull and it migrates the database while it is still behind. Do not reach for either to get past the stop.

Shared servers

A server-mode database is served to every bd client connected to that dolt sql-server, so a schema migration is not a local event: it promotes the schema version for all of them at once, and clients still running an older bd refuse the database until they are upgraded too. bd therefore never auto-migrates a shared database on a version bump — with or without a remote configured, though the two cases consent differently (#5920). Upgrade one server’s clients like this:
Between steps 1 and 2, an upgraded client reads normally and its writes are refused with the gate’s guidance. Nothing is silently promoted, so there is no deadline — but the window is a degraded one, so keep it short. If the server’s schema is v53 or older (1.1.x, or 1.2.2, the re-release of the 1.1 line), reads can fail too until step 2: bd list stops with table not found: leases (#6929). Run step 2 as soon as the clients are upgraded. If the shared server also has a Dolt remote, step 2 is not enough. Two hazards now apply at once — the co-resident lockout above and the cross-clone fork of Remote-backed databases — so bd requires the stronger designated-migrator consent it describes: bd migrate --force, from exactly one machine, followed by bd dolt push. If another clone has already migrated and pushed, adopt its database instead of migrating, as described under Remote-backed databases — bd bootstrap alone does not replace an existing database. On a shared server, adopting also promotes the schema for every client of that server, so step 1 still comes first either way.
Migrating is one-way for the fleet: after step 2, a client still on the older bd refuses the database until it is upgraded. Do step 1 first, and confirm it — the gate cannot see the other clients’ versions and will take your word for it.
A new client joining a server whose schema is behind is refused before its workspace is written, so there is nothing to run bd migrate schema from there: do step 2 from a client that is already set up, or join and migrate in one step with BD_ALLOW_REMOTE_MIGRATE=1 bd init …. The same rules apply in proxied-server mode, which is a shared server reached through a local proxy. Three differences worth knowing:
  • read commands print a warning and keep serving the current schema, rather than the read simply not touching it;
  • bd serve refuses to start against a database with pending migrations, because a daemon has no operator to consent for it. Reconcile the schema first with step 2, then start the daemon — or, for an unattended service, put BD_ALLOW_REMOTE_MIGRATE=1 in its environment as an explicit, auditable standing consent;
  • the data-behind stop’s remedy, bd dolt pull, cannot be run from the proxied workspace: run it on the server host first, then consent from the proxied workspace (see Clone behind the remote).

Cross-era Upgrades

If you’re upgrading from a much older version of bd, inspect the storage layout and metadata before running the current binary. A .beads/dolt/ directory alone does not identify a legacy workspace: supported current server mode uses that directory too. Current bd evaluates explicit server metadata, the presence of that local root, and the bounded .local_version witness together. An explicit server selection is not overridden by a stale .beads/embeddeddolt/ repository. The witness is whatever bd held in its own version string when it last touched the workspace, so it may be a plain release, a release candidate, a build carrying metadata, or a Go pseudo-version; all of those are read as the version they name. A witness that is present but unreadable is not treated as a legacy marker — no pre-v1 bd could have written one — so bd warns and continues rather than refusing every command. A missing witness stays ambiguous and is still refused. Current bd refuses recognized historical SQLite and legacy Dolt layouts before opening storage or rewriting metadata. This is intentional: preserve the source and complete the matching explicit migration below. PostgreSQL and MySQL are removed backends, not supported cross-era upgrade paths. Current bd refuses metadata that selects either backend, and the sealed bridge below accepts SQLite sources only.

.beads/embeddeddolt/: direct upgrade

Upgrade the binary, apply the pending schema migrations, and update the database metadata:
If the project was initialized before bd init automatically wired git origin as the Dolt remote, verify the remote after upgrading:
When the list is empty, fix it on the machine whose local database is authoritative:
Commit the resulting .beads/config.yaml change so other clones can run bd bootstrap or bd dolt pull.

Historical Dolt server mode: explicit migration

Do not run current bd init --force when .beads/dolt/ has missing metadata or persisted dolt_mode is blank/embedded. Those old embedded layouts are never current embedded storage. The same explicit path applies when metadata selects backend: dolt, dolt_mode: server and .local_version records v0.55.4 through v0.62.0. First take a native snapshot while every writer is stopped. Restore that snapshot to a disposable workspace, export it with the verified historical binary, then import the export into a fresh current project. Keep the original and snapshot unchanged until the cutover has been reviewed. The SQLite sealed-copy helper below does not start or manage a Dolt SQL server. Explicit server metadata with a v0.55.4–v0.62.0 witness is always refused, including when there is no local .beads/dolt/ root. When that root does exist, the guard admits explicit server mode only with a syntactically valid witness whose major version is 1 or newer; a missing, malformed, or pre-v1 witness fails closed. Without the local root, a missing, malformed, or non-historical witness is admitted only as a compatibility layout. The configured shared-server compatibility path applies only when persisted metadata is missing or leaves dolt_mode blank/embedded; it does not override an explicit server selection with a local root. Compatibility admission cannot prove that a workspace is modern. If you know it was created by v0.55.4 through v0.62.0, use this explicit bridge even when its witness was lost or damaged. Otherwise, follow the normal bd migrate --dry-run and bd migrate schema flow for an admitted server workspace.

One .beads/*.db file: sealed SQLite bridge

The old binary stored data in SQLite. The new binary uses Dolt. Recommended: use the sealed-copy bridge (requires jq): Stop every process that can write the old workspace before starting. Run this from a source checkout at the exact commit you intend to run; installed binaries do not include repository scripts. Record that commit with git rev-parse HEAD before executing the script. Download the old bd asset only from the official gastownhall/beads release and verify the asset with its published SHA-256:
On macOS or BSD, use shasum -a 256 on the downloaded archive and compare the result with that archive’s entry in checksums.txt.
For a source older than v0.49.6, also supply an authenticated v0.49.6 binary:
Verify the historical binary against its official release checksum before running the bridge. The script verifies each binary’s reported version, rejects Dolt and ambiguous layouts, retains a sealed source copy, and compares the candidate export with the canonical historical export. Activate the cutover manually only after reviewing those retained artifacts.

Troubleshooting Upgrades

Hooks out of date

Database schema changed

Recovery after upgrade

If you need to restore from a backup:
Or pull from a Dolt remote: