Skip to main content
Last reviewed: 2026-10-01 Freshness source: cmd/bd/init.go, cmd/bd/init_safety.go, cmd/bd/init_safety_test.go, cmd/bd/init_safety_help.go, and cmd/bd/dolt.go. This document lives next to the ADRs and matches the structure of bd’s error messages: each named refusal in bd init and bd dolt push/pull has a labeled anchor here with step-by-step recovery instructions. The bd dolt push/pull fork refusal deep-links its anchor directly; since #5310 the bd init refusals point at bd help init-safety, which links this document as a whole — match those by exit code and symptom text. See also: bd help init-safety, and ADR 0002 — bd init safety invariants.

Table of contents


init-force-refused

Exit code: 10 (ExitRemoteDivergenceRefused) Symptom
Why this happens bd init --force (or --reinit-local) tells bd to bypass the local data-safety guard, and only that guard: neither flag bypasses this refusal or the missing server-side database refusal. bd init --from-jsonl selects a local JSONL export as the source. But the remote already has project history. Proceeding would create an orphan local Dolt branch with no common ancestor on origin. The next bd dolt push would either fail (no common ancestor) or — worse, if force-pushed — destroy the team’s data. Recovery paths Pick the one that matches your intent.

1. You want to adopt the remote’s history (most common)

This clones the remote’s Dolt database into a fresh local .beads/. Your local state is ignored; the team’s history becomes yours. If you set aside the old .beads/dolt instead of deleting it, or bd list fails right after this with table not found: leases, see re-clone-gotchas below before you do anything else.

2. You want to diagnose what went wrong before deciding

bd doctor walks the local + remote state and names concrete problems. bd dolt status shows the Dolt-level view. Neither modifies anything.

3. You intentionally want to overwrite the remote’s history (destructive)

This is a cross-boundary operation that affects every collaborator. You need to pair the local-source init (--reinit-local or --from-jsonl) with --discard-remote. In interactive mode bd will prompt for confirmation; in non-interactive mode you must supply a --destroy-token. See bd help init-safety for the token format. After bd init --reinit-local --discard-remote, your next bd dolt push must be a history-replacing push. Coordinate with your team before doing this.

init-token-missing

Exit code: 12 (ExitDestroyTokenMissing) Symptom
Or, re-initializing over existing local issues with no TTY:
Why this happens You’re running non-interactively (CI, agent, piped input) and asked for a destructive re-init. Destructive operations cannot be authorized silently, so bd requires --destroy-token in place of the interactive confirmation it cannot prompt for. Both destructive paths need the token, not just the cross-boundary one:
  • --discard-remote, which would discard the remote’s Dolt history.
  • plain --reinit-local over existing local issues, which would destroy them (see init-local-exists).
Recovery paths

1. Run interactively

Re-run in a TTY. bd init --reinit-local --discard-remote will prompt you to type the destroy-token at confirmation time.

2. Supply the token explicitly (CI/automation)

The token format is DESTROY-<issue-prefix>. For a project whose issue prefix is bd:
Automation should template the token from project state, not from error output. See ADR 0002 — Invariant 4 for why the token is never echoed in bd’s error messages.

init-local-exists

Exit code: 11 (ExitLocalExistsRefused) interactively; 12 (ExitDestroyTokenMissing) non-interactively Symptom Interactive (TTY): you declined the typed destroy N issues confirmation. This is the only path that exits 11.
Non-interactive (CI, agent, piped input) — note this exits 12, not 11:
Why this happens Local .beads/ has existing issues. bd init --reinit-local (or its deprecated alias --force) would permanently destroy them, so bd demands an explicit confirmation first: the typed prompt in a TTY, and a --destroy-token when there is no TTY. This applies to --reinit-local on its own — --discard-remote is not required to trigger it. A plain bd init over an initialized workspace does not reach either code: the local-safety guard refuses it with an ordinary error before this point. Recovery paths

1. Export first, then proceed

issue-export.jsonl lets you re-import individual issues if needed. It is not a full database backup; use bd backup when the Dolt database is healthy enough to create a restorable backup before reinitializing.

2. Investigate why you hit this

If you did NOT expect bd init to be the right command here, run bd doctor first — you may be looking at a server config issue that a re-init won’t fix.

init-missing-server-db

Symptom
What happened This workspace is in server mode and shows evidence of a prior bd init: .beads/metadata.json carries a project_id, or (under --reinit-local or --force) a workspace that predates project_id still has a local Dolt data directory, in which case the message cites that instead of metadata.json. Yet its configured database is not on the server, or the server could not be reached to confirm. That is treated as a recovery situation, not a fresh clone, and the two are otherwise indistinguishable from the local filesystem alone: this project’s own database directory is absent either way, because it was never created here or because it was lost with the local storage. bd init used to resolve the ambiguity by assuming “fresh clone” and creating the database. When the assumption was wrong, the result was a new, empty database sitting at the name the real one used to occupy — the 2026-08-11 fleet-wide data loss. bd init now refuses instead, and the refusal is not bypassed by --force or --reinit-local. Those flags authorize destroying a database that exists; they do not authorize inventing an empty one where the configured database has gone missing.

1. Find out where the database went (do this first)

A stopped server, a server started on the wrong port, or a wrong --data-dir all present exactly like this — and none of them need a re-init. Fixing the server brings the data back untouched.

2. Restore from an export

Also check .beads/backup/ for a JSONL export you can import manually.

3. Create a fresh empty database at this name

Destructive to any unrecovered data. Only when you are certain no recoverable data exists:
--recreate-missing is per-invocation only. It is deliberately never implied by --force, --reinit-local, a config key, or an environment variable, because it authorizes the one thing this guard exists to prevent. The refusal names --recreate-missing but does not print this command: it also fires when the server merely could not be reached, so it sends you through steps 1 and 2 here instead of handing you a line to paste. See ADR 0002 — Invariant 4. Limits worth knowing Whether a workspace meets this refusal depends on two pieces of local evidence: whether this project’s own database directory (<dolt-data-dir>/<database>/.dolt) exists, and whether metadata.json carries a project_id (introduced by GH#2372). With the configured database missing from the server, or the server unreachable: Adding --recreate-missing lifts every refusal in the last four rows, including the third row’s “already initialized”. It changes nothing in the first row, where this project’s database is still on local disk. In shared-server mode the data dir is machine-global (see below), so the last two rows rarely apply there.
  • project_id is also inherited by a fresh clone, because .beads/metadata.json is git-tracked by default. Cloning a beads-managed repo whose server-side database does not exist yet therefore meets this refusal rather than a plain first init. It fails in the safe direction — nothing is created silently, and no existing data can be stranded — and --recreate-missing above is the supported way through it. This is settled, not pending: engdocs/adr/0004-missing-database-guard-signal.md records why no purely-local signal can replace project_id here (absence of local state cannot tell “never initialized here” from “initialized here, storage since lost”), and why --recreate-missing is the permanent resolution path rather than a stopgap.
  • Server mode only, and shared-server mode counts as server mode — the guard tests for this project’s database directory under the resolved Dolt data dir, not for the data dir itself, which in shared mode is the machine-global ~/.beads/shared-server/dolt that is created on demand.
  • Proxied-server mode does not reach the --reinit-local/--force guard — the --proxied-server dispatch in runInit (cmd/bd/init.go) returns before it. It does reach the plain-init guard, because runInitProxiedServer calls checkExistingBeadsData, so --recreate-missing works under --proxied-server too: the flag is assigned beside its flag read, upstream of every dispatch in runInit. Keep it there. It was previously assigned next to the guard it gates, which is downstream of the proxied dispatch, and the flag was therefore inert on that one route while the refusal still told the operator to pass it.
  • bd bootstrap has its own separate mode-blind create path.

pk-fork-refused

Symptom
(or the variant without in its common ancestor). bd follows the error with a short version of the recovery recipe below. Why this happens The two histories being merged disagree about a table’s primary key set — not about row contents. Dolt can cell-merge rows, but it refuses outright to merge a table whose primary key was reshaped differently on each side (or whose common ancestor had a different primary key than both sides). The refusal happens before any row conflicts materialize, so bd dolt pull’s conflict auto-resolver never gets a chance to run. Retrying never helps: the histories are permanently un-mergeable. The usual cause is upgrading bd independently on two clones while un-synced changes existed on both sides, across a release whose schema migrations reshape a primary key. Concretely: the #4259 incident — clones straddling the 0041/0043/0050 reshapes of dependencies (v1.0.4 → v1.0.6) hit exactly this on the first post-upgrade pull if both sides had unpushed dependency edits. The remote-migrate prevention gate (v1.0.6+) exists to stop this from being created: it refuses to auto-migrate a remote-backed database and tells you to designate a single migrator. This playbook is for when the fork already exists. Recovery: bootstrap from one canonical clone The forked histories cannot be merged, so one side must be chosen as canonical and every other clone re-cloned from it. Issue data survives via JSONL export/import; only the un-mergeable Dolt history is discarded on the non-canonical clones.

1. Pick the canonical clone

Usually the most complete / most recently active clone. To compare, run on each clone (read-only):

2. On the canonical clone: upgrade, migrate, force-push

(bd’s migration gate may block here; that is exactly the designated-migrator case the gate is asking about — follow the guidance it prints on the canonical clone.)

3. On EVERY other clone: save local-only work, re-clone, re-apply

bd import has upsert semantics: issues that only existed on this clone are re-created, newer local edits are applied, and rows older than what the remote already has are skipped. Spot-check with bd stats afterwards. Doing this by hand (moving .beads/dolt aside instead of rm -rf, or skipping straight to bd list) can hit either of two live gotchas — see re-clone-gotchas below.

Prevention (upgrades across PK-reshaping migrations)

  • Sync before upgrading: bd dolt push + bd dolt pull on every clone while all clones still run the old version, then stop editing. Once the new binary is installed, bd dolt push/bd dolt pull are gated too, so this must happen first. The one exception is the data-behind stop — a clone level on schema but missing commits the remote has — where bd dolt pull is the remedy and is allowed through (a proxied-server workspace refuses it, so there the pull runs on the server host); the gate says so when it fires, and Clone behind the remote has the recipe. Plan for the rule, not the exception.
  • One designated migrator: upgrade one machine, let it migrate, then bd dolt push.
  • Every other clone takes the migrated database, does not migrate: after the migrator pushes, each other clone runs bd dolt pull while still on the old binary, then upgrades. Once the new binary is installed that pull is refused while the clone has pending migrations, and bd bootstrap never replaces an existing database — on its own it reports Nothing to do. — so a clone that upgraded first must move its local database aside before bd bootstrap can re-clone. The recipe is in Remote-backed databases and multiple clones.

re-clone-gotchas

Two gotchas hit during a live manual re-clone recovery (issue ga-vrq5pu), each of which cost real time because the symptom looks nothing like the cause. Both apply any time you set aside or replace a Dolt database directory by hand — during the pk-fork-refused playbook above, the init-force-refused bd bootstrap path, or any other manual re-clone.

Gotcha 1 — a damaged/set-aside store must go OUTSIDE data_dir

Symptom
…printed repeatedly as the Dolt sql-server crash-loops under its supervisor/watchdog. Nothing in that message mentions a stray directory, so it does not look like “you left a directory lying around.” Why this happens The sql-server treats every subdirectory of its data_dir (default .beads/dolt/, overridable via BEADS_DOLT_DATA_DIR or the dolt_data_dir field in metadata.json) as its own database and tries to load it. If you move a damaged or superseded database directory aside but leave it inside data_dir (for example, renaming .beads/dolt/mydb to .beads/dolt/mydb.bak instead of moving it out of .beads/dolt/ entirely), the server tries to load the damaged copy too and dies on it — even though the healthy database sitting right next to it is fine. Check which mode you are in before you go looking for data_dir: in shared server mode (BEADS_DOLT_SHARED_SERVER=1, or the dolt.shared-server config key) it is ~/.beads/shared-server/dolt/ — or $BEADS_SHARED_SERVER_DIR/dolt/ — and that takes precedence over both of the per-project knobs above. That is also where this gotcha bites hardest: one shared data_dir holds every project’s database on the machine, so a single stray subdirectory crash-loops the server for all of them. The fix When you set a database directory aside by hand, move it outside data_dir — e.g. up to /tmp/ or a sibling of .beads/, never to a sibling path still under .beads/dolt/. Do not wait for an automated repair to do it for you: no bd doctor --fix repair performs this move here. The two that come closest are:
  • The corrupt-manifest repair renames the damaged database’s own .dolt/ directory in place — <data_dir>/<db>/.dolt → <data_dir>/<db>/.dolt.<ts>.corrupt.backup — and reinitializes beside it, so in the standard <data_dir>/<db>/ layout the backup is nested inside the database directory rather than becoming a new direct child of data_dir. It also fires only once its scan can prove the store holds no recoverable chunk data, so it will not touch the superseded-but-populated copy this gotcha is about (internal/doltserver/manifest_recovery.go).
  • The rename that does move a whole data_dir to a timestamped sibling (data_dir → data_dir.<ts>.corrupt.backup) belongs to a different repair, the database-integrity recovery (cmd/bd/doctor/fix/database_integrity.go) — and that one refuses outright for a repo configured in Dolt server mode, so if the crash-looping server is your configured backend it is unavailable too.
Make the move by hand, and make sure it lands outside data_dir.

Gotcha 2 — a fresh clone needs bd migrate schema

Symptom
(or a similar “table not found” error for wisps, events, local_metadata, or another clone-local table). A supervisor or agent harness that expects to load session beads right after a fresh clone fails here. Why this happens A handful of tables — leases, wisps, wisp_*, events, bd_events_*, local_metadata, ignored_schema_migrations, repo_mtimes — are dolt-ignored, clone-local tables: they exist on a running database but are deliberately excluded from what bd dolt push/pull/clone transfers, so a fresh clone starts without them. A writable open normally re-materializes them on its own, whether it is embedded or reaches the database through a server or proxied sql-server: it runs the schema migration pass, which replays the clone-local (“ignored”) series whenever that series’ cursor table is behind — and the cursor table, ignored_schema_migrations, is itself clone-local, so a fresh clone always qualifies (internal/storage/schema/schema.go, internal/storage/uow/dolt_sql_provider.go). Reaching the error above therefore means the open that hit it was not one of those self-healing opens. The kinds that skip the pass, or only verify, include a deliberately non-mutating read-only or preview open, a team-server open (it checks the schema rather than migrating it), the preview-attach path, and any open whose open-time migration gate refused. Run the migration explicitly to close the gap. The fix
No --force needed. This replays the clone-local tables and prints:
That output is expected and reassuring, not an error — it means the versioned schema was already current; the clone-local tables have now been (re)created regardless. Run this once after any fresh clone or bd bootstrap, before relying on bd list or any other command that reads session state.