v1.3.0-rc.1
Pre-releaseBeads v1.3.0
v1.3.0-rc.1 — release candidate for v1.3.0. Changelog section dated 2026-08-28; cut from
release/1.3.0 at b3ef65c8.
Beads 1.3.0 is the first tested release off main since the 1.1 line, and it carries everything
that has landed since — 1,342 commits arriving in a single upgrade.
That is not the usual increment, and the reason is worth stating plainly. The v1.2.0 tag burned
pre-publish on a FreeBSD cross-compile failure and was never reused, so there is no [1.2.0]
changelog section — the [1.2.1] section is the 1.2.0 content. v1.2.1 shipped on 2026-08-11, but
published by accident, without release testing, and it auto-migrated local databases to a schema
(v54–v65) no supported binary knew. Four days later
v1.2.2 recovered the line by re-releasing
the tested v1.1.2 tree under a higher version number, off branch release/v1.2.2 rather than
main. go.mod retracts v1.2.1, v1.2.0 and v1.1.1. So the version numbers moved forward while the
code did not, and 1.3.0 is where main's work reaches users for the first time.
What that means for you, concretely:
- On v1.2.2 — you are running v1.1.2-era code. You receive the entire
[1.2.1]section (2,236
changelog lines, the largest in the file) and everything in[1.3.0]. Whole subsystems below —
an HTTP API server, work leases, a durable events journal,bd sync, a public Go API — do not
exist in your build in any form. This is the upgrade to read the notes for. - On v1.1.2 — identical to the above. v1.2.2 is your tree with a different version string; the
only functional difference is a forward-skew error message. - On v1.1.0 — the same, plus the
[1.1.2]dolt#11131 aux-row-rekey drift fix (#4380), which is
a data-availability fix and is re-listed under Fixed below. - On v1.2.1 — you should not be; it is retracted.
bdwill stop withschema version mismatch.
Roll the schema cursor back first, per the
recovery runbook.
There is no 1.1.1 and no 1.2.0 upgrade path: both tags burned before publishing anything.
Read the upgrading notes before installing.
Highlights
- beads has an HTTP API server.
bd servebinds a port and answers the whole work loop over
41 OpenAPI-specified operations across 35 paths — ready/list/get/query/count/related, stats,
dependencies (list, count, tree, blocking, cycles), config, memories, events, and the writes: claim,
claimNext,
release, close, reopen, PATCH, batchCreate, batchClose, batchApply, delete, sweep, casMetadata,
dependency add/remove. Errors are RFC 9457problem+jsonwith a frozen machine-readablecode
vocabulary and one HTTP status per code, so a client classifies a claim conflict from a typed 409
instead of substring-matching error prose; listing pages with an opaque keyset cursor that does not
expire and survives restart;GET /v0/beads/contextreports which operations the running build
actually implements. The spec is hand-written and checked in, the Go wire types are generated from
it, andmake api-checkfails a change that edits one without the other. This is 100% net-new on
upgrade —internal/httpapi/spec/openapi.v0.yamldoes not exist at v1.1.2 — and it is the
subsystem the current release notes had been missing entirely. bd serveis deployable, not just a loopback toy (#5516).--auth-token-filenames a file of
accepted bearer tokens, one per line; every operation exceptGET /healthzthen requires
Authorization: Bearer <token>, includingGET /v0/beads/context. The file is re-read while the
server runs, so revocation — not just rotation — is a file rewrite with no restart, and a
failed or empty re-read keeps the last-good set. There is deliberately no--auth-tokenflag: a
credential in argv is readable out ofps.--allow-non-loopbacknow requires a token file, with
--insecure-no-authas the explicit auditable opt-out, and--allowed-hostextends the
DNS-rebinding allowlist. Read the omissions as contract: there is no TLS (the deployment
supplies confidentiality), a token is a shared secret granting the whole surface rather than an
identity,actorstays caller-asserted provenance, hooks do not fire on HTTP mutations, and the
surface includes destructive operations (issues:sweep,issues:delete).- A multi-agent coordination layer, from claim to recovery. A claim used to be permanent: a
worker that died mid-task stranded its beadin_progressforever with no recovery verb. Claims now
carry a lease —lease_expires_at(default TTL 5m) andheartbeat_at, schema v54 — with
bd heartbeat <id>to extend it,bd reclaim --older-than <dur>to revert expired ones back to
ready, andbd unclaimto give one back (#4537). Because Dolt has no row locking and merges
concurrent commits cell-by-cell, every ownership-mutating path also rewrites a sharedrow_lock
cell, forcing a racing heartbeat-vs-reclaim into a serialization conflict the retry layer replays
rather than cell-merging into a zombie claim; the same landing wrapped the work-queue hot paths in
serialization-conflict retry, so N workers draining one queue stop surfacing raw MySQL 1213/1205
errors. Leases are replica-aware:leases.granted_noderecords the granting replica andbd reclaimskips a lease another replica granted unless you pass--any-replica(wy-jpd3.7). The
guard is opt-in and fail-open, armed bynode_id/BEADS_NODE_ID, so an upgrade can never strand
a lease the reaper could previously recover. - Compare-and-set updates close the two coordination transitions no verb could express
(bd-wsqvw).bd update --if-assignee/--if-statusapply only if the bead's current value still
equals the expected one — one atomic transaction, nothing written on a mismatch.--if-assignee ''
means "expected unassigned". A stale guard is machine-distinguishable from infrastructure failure:
exit code 13 when every failure in the run was a guard mismatch (a racer won — skip gracefully)
versus 1 for anything else, with"guard_mismatch": trueper failed entry under--json. That is
what makesbd update <id> --if-assignee worker -a mayor— reassign X→Y only while X still holds
it — safe without a read-then-write race.bd unclaim --if-assigneeis the inverse spelling, and
claim.pools(bd-bguz6) turns the dispatcher-fleet pattern into a one-step atomic take: aliases
listed inbd config set claim.pools "fable-crew,night-crew"become claimable by any actor through
the same compare-and-swap, while beads assigned to a real actor keep their anti-steal protection. bd syncis the federation loop as one verb (wy-jpd3.4). Pull, detect conflicts, recompute
is_blocked, push, with bounded retry (default 3) when another replica wins the push race. Two
properties are the point. Conflicts are detected positively, from the merge's own captured
conflict rows and fromdolt_conflicts, never inferred from the pull's exit status — a pull fails
for plenty of reasons that are not conflicts, and a settled-but-conflicted merge aborts leaving
dolt_conflictsempty, so an exit-status guess invents phantom conflicts and misses real ones. And
a conflict it cannot settle is never resolved by picking a side: it halts before recomputing or
pushing, with no--strategyoverride. The recompute between pull and push is not bookkeeping —
is_blockedis denormalized, so a merge that brings in another replica's dependency edge leaves
bd readystale until it runs, which is precisely the step a hand-rolled shell loop omits. Exit
codes are the machine contract: 0 synced, 1 error, 2 merge conflict (halted, nothing pushed), 3
push-race retries exhausted, and — new in 1.3.0 — 4 for a dirty working set that is stuck rather
than busy.bd conflicts list|show|resolveis the companion that clears an exit-2 halt without
dropping into the rawdoltCLI.- A durable events journal, and
bd eventsto read it (bd-opisf). Every committed bead mutation
writes one ordered record in the same transaction as the mutation, carrying the operation, the
mutated id, and the bead's full post-mutation snapshot includingis_blocked— so a downstream
mirror stays correct without re-querying the graph. Off by default; opt in per workspace with
bd config set events-journal trueorBD_EVENTS_JOURNAL=1.bd events tail --since <seq>prints
JSON lines and--followstreams as writes commit;bd events exportprints from the beginning;
bd events prune --before <seq>takes an earlier cut. A read that cannot resume fails rather
than lying: a--sincebelow the oldest retained record exits 1 with a typed
events_journal_truncatederror carryingsince,floorandhead. Retention floors
(events-journal-retain-days7,events-journal-retain-rows100000) are enforced by a throttled
prune that can never fail a command. Scope it correctly: the journal is clone-local working-set
state (dolt_ignored) — never versioned, never pushed or federated, per branch and per replica,
so a checkpoint from one replica is meaningless against another. External tooling previously had
only fire-and-forget hooks (which may not run) or snapshot polling (which misses anything that
changed twice between reads); this is the third option. - The biggest lever on agent token cost in the whole arc:
--briefand--brief-deps(#5546,
#5547, #5549, #5554, #5586). On one real 16,051-bead storebd ready --jsonreturned 454,021
bytes, with notes plus description accounting for 76% of it — on exactly the call an agent makes
to decide what to work on next.--briefonbd listandbd readyomits the free-form text
(description,design,acceptance_criteria,notes,payload,waiters) and measured
93.4% smaller. Separately, onebd show --jsonreturned 214,456 bytes of which 193,039 was
thedependenciesarray, one dependency carrying a 180,975-bytenotesfield;--brief-deps
projects each dependency toid,title,status,issue_type,priority,dependency_typeand
measured 89.3% smaller. Both are opt-in, both have HTTP twins (brief,brief_deps), and both
are refused wherever they could not be honored rather than accepted and dropped. One caveat to
design around: the JSON response carries no marker for the omission — an omitted field is
indistinguishable from a genuinely empty one, so only the caller that passed the flag knows its rows
are partial. - One guided, crash-resumable migration from v53 to v66. On an embedded or local store, the first
invocation after installing migrates your schema in place — 13 main-series migrations plus 15
clone-local ones, about 28 migrations, with the counter visibly restarting partway through (that
is the clone-local series, not a loop). Back up first, with the binary you have now. - A shared Dolt sql-server is never auto-migrated. Migrating one promotes the schema for every
attached client at once, so a version bump there now waits for explicit consent —bd migrate schemaafter the fleet is upgraded, orBD_ALLOW_REMOTE_MIGRATE=1in scripted use (#5920, #6048).
The gate also runs on the proxiedbd serveopen path, which had been migrating shared databases on
every open, reads included (#6055), and is scoped so a server bd auto-starts for a single workspace
still migrates on open as it always has (#6088). Write commands respect aMIGRATION-FREEZE
sentinel with a dedicated exit code 14 (#6043), andbd doctorhonors--readonlyand the freeze
instead of--fix-ing through them (#6056). - Data-integrity heals across the Dolt plane. Duplicate typed dependency edges merge instead of
aborting the rekey half-applied (#6045), a legacy tracked migration-cursor table is untracked at
open sobd dolt pullunwedges (#6046),bd deleteno longer wedges auto-export forever (#6059),
an ignored-series sentinel gap no longer replays the whole clone-local chain and restamps wisp
timestamps (#6054), and server-modeDOLT_ADD/DOLT_COMMITnow run after the SQL transaction
commits, ending concurrent lost updates (#6040, carrying @nova-submodules' #5740). - Two long-standing correctness bugs that were silently costing you work. A dated defer now means
what it says:--untilsetstatus=deferredand a timestamp, but nothing ever flipped the status
back, so an expired defer stayed invisible tobd readyforever — one deployment measured 241
beads, including P1s, silently dark this way, regenerating daily from correct-looking automation.
Ready-front reads now run a lazy wake sweep first (bd-i8qx8). And--label-anywas emitted by
bd listbut silently dropped bybd readyandbd ready --claimon every backend, so a worker
fencing itself to its own lane (bd ready --claim --label-any lane-a --parent epic-1) would happily
claim another lane's bead and believe it was fenced (bd-s10oa). - JSON contracts fixed before 1.3.0 freezes them. String revision tokens,
--set-metadatascalar
typing, andbd create --jsonnano timestamps (#6053);--storage-classwired through the proxied,
markdown, and graph create paths (#6060); journal delete and cascade rows carry the request actor
(#6061); andGET /v0/beads/issuesgainssortwith order-bound cursors (#5666). - Errors tell the truth.
bd bootstrapprobes git remotes for Dolt data instead of rejecting
them, and exits non-zero when it declines (#6037); proxied/gatewaybd initattributes identity
safely and classifies access denials honestly (#6062); the legacy-backend tombstone names the exact
heal instead of destructive advice (#6044); a Homebrew--HEADversion stamp is recognized rather
than misread as0.0.0and routed into a.dolt-deleting recovery (#6079);bd primesays when
the memory plane could not be read (#5877). - Security posture for the tag. The Go toolchain moves to 1.26.7, closing seven stdlib advisories
reachable from the shippedbdbinary, alongside dependency bumps clearing all 25 open advisories
with per-cluster reachability verdicts (#6047). Carried from the same arc:bd dolt pushand
bd syncno longer adopt a git-origin-derived Dolt remote without consent (#5068), the settings
plane no longer serves thekv.memory plane by any read (bd-rfwtv, bd-klko9), and the no-ID
"last touched issue" fallback is interactive-only (#4839). - The upgrade path is tested where it bites. A new wisp-plane upgrade corpus seeds and asserts the
clone-local plane — wisps, leases, events, the ignored-migration cursor — that the 1.3.0 chain
actually rewrites (#6049).
Upgrading Notes
-
On an embedded or local store, the first invocation migrates your schema, in place, from v53 to
v66. A v1.2.2 (or any 1.1.x) database sits at main-series schema v53; this binary knows v66, so
the first command that opens the store applies 13 main-series migrations,
0054_add_lease_columnsthrough0066_add_events_journal_actor. Two of those passes rewrite rows
rather than just reshaping tables — the aux-row id rekey and theeventsdolt_ignore flip described
under Changed — so on a large store the first invocation is noticeably slower than the ones
after it. It is crash-resumable and picks up where it left off, but do not interrupt it if you can
avoid it. -
A shared
dolt sql-serveris never auto-migrated (#5920, #6048).
A server-mode database is served to every client attached to it, so migrating it promotes the schema
for all of them at once and locks out anything still on an older binary. Upgrade that server's
clients first, then consent once:# 1. upgrade bd on every client of the server; reads keep working throughout bd version # on each client, confirm the new version # 2. once, from a workspace already set up against this server: bd migrate schema # add --global for the shared global database # 3. confirm bd doctor
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 keep the window short.
BD_ALLOW_REMOTE_MIGRATE=1is the scripted, auditable standing consent, and a client joining a
behind-schema server is refused before its workspace is written, so join and migrate in one step
withBD_ALLOW_REMOTE_MIGRATE=1 bd init …. If the shared server also has a Dolt remote, step 2 is
not enough: two hazards apply at once and bd requires the stronger designated-migrator consent
(bd migrate --forcefrom exactly one machine, thenbd dolt push). Recipes in
Shared servers. -
The counter restarts partway through, and that is not a loop. The clone-local (dolt_ignored)
series runs after the main one, through the same printer and its own numbering, and it moves 0011 →
0026 on this upgrade. So the run is about 28 migrations, not 13, and what you see on stderr is
13 lines counting up to 0066 followed by 15 lines starting again at 0012:Applying migration 0065: widen_wisp_comments_text… Applying migration 0066: add_events_journal_actor… Applying migration 0012: create_leases… ← clone-local series, not a restartA counter that jumps backwards is the signature operators kill runs over. Let it finish. Progress
prints only when stderr is a terminal — piped and CI runs see nothing at all, which is deliberate: a
silent-looking CI upgrade is not a stuck one. -
Back up first, with the binary you have now. Take the backup before you install 1.3.0. Under
the new binarybd exporttriggers the auto-migration before it exports, so a snapshot taken
afterwards is a post-migration snapshot and cannot protect you against the migration going wrong. On
a remote-backed store, finish syncing with the old binary too: once 1.3.0 is installed the
pending-migration gate refusesbd dolt pushandbd dolt pullas well, not justbd migrate.# with your CURRENT bd, before installing 1.3.0: bd dolt push # remote-backed stores only bd export --all -o .beads/backup/pre-1.3.0-$(date +%Y%m%d).jsonl
A JSONL export is cheap, issue-complete, and importable by any bd version. If you want a Dolt-native
snapshot that keeps history and config, configure a destination and sync it — barebd backup
takes no backup, it is a command group that prints help and exits 0:bd backup init <path-or-dolthub-url> # once, to configure a destination bd backup sync # take the snapshot
-
Upgrade every client that shares a store, together. The forward schema-skew guard means an older
co-resident binary — a secondbdearlier inPATH, a long-runningbd serve, another clone's
cron job — refuses a database migrated past what it knows, rather than proceeding blind. That is the
guard working, not a bug, but it makes a mixed-version fleet a broken fleet: one machine running
1.3.0 takes the whole store forward and every 1.2.2 client stops. Runwhich -a bdafter
installing, and on a remote-backed store follow the designated-migrator procedure (one clone migrates
and pushes; the rest pull or re-clone). See
Upgrading for the per-install-method recipes
and the multi-clone flow. -
Expect your ready front to grow, and your first purge to clear more than usual. Two fixes in
this arc surface work that was previously unreachable. Expired dated defers now wake back onto the
ready front (bd-i8qx8) — if you usebd defer --untilat all, beads you believed would come back
have probably not been coming back, and they reappear after upgrading. Andbd purge/bd prune
now select candidates by tier (#5995), so wisps minted before theephemeralcolumn was set — one
production database held 858 of them, reachable by no sweep — are finally swept. -
Dolt is the only storage backend; SQLite, PostgreSQL and MySQL are gone. If you read the
[1.2.1]changelog section directly, note that its "Storage backend scope simplified" entry states
SQLite remains a supported storage path. That sentence is stale and does not describe this
release. The direct PostgreSQL and MySQL adapters were rolled back before ever entering a tagged
release; SQLite was removed in the open-core split. At the tag the supported paths are embedded
Dolt, Dolt server, and any conformance-passing backend registered through the publicbackend
package.sqlite,postgresandmysqlsurvive only as recognized-and-rejected names so a stale
"backend"field in.beads/metadata.jsonproduces a targeted heal instead of a confusing failure
(#6044), andbd migrate legacy-sqlite --source-db PATHis a read-only JSONL extraction path off an
old database. -
If you need to go back, the rollback is a schema-cursor rollback, not a downgrade of the data:
the procedure is written up in the
recovery runbook (repo copy:
docs/recovery/accidental-1-2-1-release.md). Its worked example is the v53↔v65 case from the
accidental 1.2.1 release; the steps are the same for v66, with the version numbers adjusted. -
After upgrading,
bd upgrade reviewprints exactly the entries between the version you were
running and this one. Prefer it tobd info --whats-new, which dumps the entire release history.
Several commands changed defaults, so read it before your first session.
Breaking changes for v1.2.2 users
This is a highlights list, not the complete set. A v1.2.2 user is crossing two releases at once,
and the breaks below are the ones most likely to stop a script or a service. The full set is the
[1.2.1] section of the
CHANGELOG plus the Changed
section here — read [1.2.1] in full before upgrading, since v1.2.2 withheld it and v1.2.1 itself was
pulled, so nobody on the supported line has seen it.
Carried from [1.2.1], never shipped to v1.2.2 users:
bd update --status <done-status>now enforces close policy — open children or a live direct
blocker refuse the move, matchingbd close. Override withbd update --force; an unforced refusal
rolls back the entire batch.bd searchincludes closed issues by default (bd-t5yex). Narrow with--status openfor the
old behavior;bd listkeeps its open-only default. Note the trade: matches beyond--limit
(default 50) are dropped under a status-blind sort, so a broad query on a large database can now
fill the page with closed matches and silently drop open ones.- The no-ID "last touched issue" fallback on
bd update/bd closeis interactive-only (#4839).
A scriptedbd update $ID …with an empty$IDnow refuses instead of mutating whatever was
touched last. SetBD_LAST_TOUCHED_FALLBACK=1if a script genuinely relied on it. bd human listhides done/frozen and pinned beads by default, and validates--status(#5332).
A--statustypo is now an error rather than an empty list.bd dolt pushandbd syncno longer adopt a git-origin-derived Dolt remote without consent
(#5068). Adoption now prompts (defaulting to no) and fails closed non-interactively;--yes/-y
consents ahead of time, and--no-adopt/BD_NO_REMOTE_ADOPT=1disables it entirely and wins over
--yes. CI and cron that relied on the silent adoption will now fail closed.bd --readonly serveis refused instead of binding a server that cannot do what it advertises.
Anything scripted as a "safe" read-only server does not start after this upgrade — drop the flag.
Worth checking before you restart a long-runningbd serveas part of the fleet upgrade above.bd config listandGET /v0/beads/configno longer enumerate thekv.plane, which is where
bd remembermemories live — that closed an unauthenticated endpoint handing out every stored
memory. A later landing in the same era closed point reads too:bd config get kv.memory.<slug>now
answers exactly as a key nothing ever stored does (echoed key, empty value, nil error), because
bd rememberderives its key from the content it stores and the names are guessable. Usebd kv,
bd recallandbd memories, which read the store directly and are unaffected;bd config set/unsetstill take a verbatimkv.key as the escape hatch for a wedged memory.bd delete --forceon a team server orphans dependents instead of deleting them (bd-x82so).
Against a proxied serverbd delete X --forceused to delete X and its whole dependent subtree,
and--cascadewas refused outright. Both routes now carry the local database's meanings: plain
bd delete Xis refused if anything outside the request depends on X,--forceorphans dependents,
--cascade --forcetakes the subtree. A script that relied on the implicit cascade will start
leaving orphans — add--cascade.bd dep cycles --jsonemits a new shape (bd-wfkbv). An array of
{"members": [{"id", "issue"?}], "partial": bool}where it used to emit an array of arrays of
issues. The report is also canonical now (members rotated so the lowest id leads, cycles sorted
against each other) and honest about members it cannot resolve, which previously vanished from the
path — so a three-node cycle could render as a two-node one and look complete.bd list --readyrefuses a filter it cannot honor (bd-yby99.8) instead of silently ignoring it.
bd list --ready --id tst-d68used to answer with every ready bead in the workspace. Refused with
--ready:--id,--title,--spec, the*-containsfamily,--external-ref, any date bound,
--deferred,--overdue,--empty-description,--no-labels,--no-parent,--pinned,
--priority-min/max. A script that passed one was already getting the wrong answer.bd list --status=allstops hiding pinned beads (#5332).allpromises every status, but the
pinned exclusion compared the raw selector string, soallfell through and kept forcing
Pinned=false— and--status=pinned,closedexcluded the pinned beads it explicitly asked for. Add
--no-pinnedto keep the old result.- A dependency type is bounded at 32 characters, not 50 (bd-yby99.3). The column on both
dependency planes isVARCHAR(32), so the old bound accepted a range no row could carry. In
practice nothing should notice — the longest well-known type,conditional-blocks, is 18 characters
— but the error text changed: messages that readmax 50 charsnow name 32. - Text-input commands refuse two sources (#5332).
bd comment,bd noteandbd comments add
used to let--stdin/--filewin over positional text and drop the positional half silently; the
dropped half was, in every case found, the text the caller meant. Naming two sources is now an
error. bd label add/removeover many beads is no longer one atomic transaction (ga-26w10, #5489).
bd label add a b c mylabelrecords three history entries instead of one, and a failure on the
third leaves the first two written where the old shape rolled them back.--dolt-auto-commit batch/offactually defer version commits in SQL-server mode (bd-4wamg).
The mode was silently inert there. If you explicitly configureddolt.auto-commit: off, it was
behaving likeon; it now genuinely stops minting version commits until an explicit
bd dolt commit. Proxied-server routes never apply auto-commit policy, so batch/off remain inert
there.- The published
backendpackage drops orphan handling (bd-gwryr). A Go consumer that names
backend.OrphanHandlingor its constants no longer compiles. Delete the option — every call site
passedOrphanAllow, which is exactly what the code still does. bd purge/bd pruneversion-control messages changed (bd-pn231). The entry is now
bd: sweep <n> <tier> bead(s)on both routes, replacingbd: prune <n> bead(s)and
bd: delete <n> issue(s).
New in 1.3.0 (full entries under Changed):
--profileis now--cpu-profile, with no alias (#5126). The old spelling fails as an unknown
flag rather than silently doing nothing.- An explicitly configured Dolt server port now outranks the ambient
BEADS_DOLT_PORTenvironment
variable. - Actor matching decodes an exact
--run to/instead of collapsing it to a generic separator, so
gastown--mayormatchesgastown/mayorand stops matchinggastown__mayor.
Added
First shipped on a tested release here — the withheld [1.2.1] work
bd serve— the beads work surface over HTTP (bd-serve v0; #5410, #5417, #5422, #5423, #5429,
#5506, #5507, #5508, #5510, #5535, #5536, #5540). One process answering 41 OpenAPI-specified
operations across 35 paths instead of abdsubprocess forked per call. Reads:context,
ready,issues
(list, get, query, count, related, comments),stats,dependencies(list, count, tree, blocking,
cycles),configandconfig/{key},memories,eventsandevents:watch. Writes::claim,
:claimNext,:release,:close,:reopen,PATCH /issues/{id},:batchCreate,:batchClose,
:batchApply,:delete,:sweep,:casMetadata,dependencies:add/:remove. Close and reopen
are idempotent and say so in the body (already_closed: true) rather than in a status.PATCH
publishes nineteen members, four of them nullable where an explicitnullclears them — a closed,
machine-checked set, so anullon any other member is a 400 rather than an unannounced clear.
expected_versionon close/reopen/delete plusrevisionon the detail read give the row's
optimistic-concurrency token, so a read-modify-write loop can start from a read rather than seeding
its first guard from a write it did not want to make. Two operating caveats: an embedded-Dolt
workspace is permanently refused (that backend commits outside the SQL transaction, so per-request
atomicity would be a lie), and--addr 127.0.0.1:0takes an ephemeral port with no mutual exclusion
— pass an explicit port. Probe readiness withGET /v0/beads/ready?limit=1;/healthzis liveness
only and stays green while the database is unreachable.- Bearer authentication and a supported beyond-loopback deployment for
bd serve(#5516). See the
Highlights entry for the full posture.BEADS_SERVE_TOKEN_FILEis the environment fallback;
401/unauthenticatedjoins the frozen code vocabulary with a fixeddetailthat never echoes the
presented credential, and missing header, wrong scheme and unknown token are deliberately one code. Bd-Project-Idrequest stamping, and theproject.enforcecapability. A client that knows which
workspace it means to address stamps the request with that workspace's project id; a server serving a
different one refuses before any database work, so a misdirected read or write mutates nothing.
An absent header is served exactly as before, so this is additive wire surface, not a new
precondition.GET /healthzandGET /v0/beads/contextare exempt — liveness must answer whatever
workspace a caller believed it reached, and the handshake is where a client learns the id to stamp
with. The refusal is the only one carryingserver_project_id, so its presence is the signal that
this check fired rather than the Host gate or a deployment's auth layer.- Work leases:
bd heartbeat,bd reclaim,bd unclaim(#4537, schema v54, migration
0054_add_lease_columns— the first of the 13 main-series migrations this upgrade applies). See
Highlights.bd reclaimalso takes the full claim-side scope surface —--label,--label-any,
--exclude-label,--assignee,--id(wy-jpd3.3) — so a supervisor can point its reaper at exactly
the partition it claims from; filters AND-combine and never widen the stale set, and a scope flag
supplied with no usable value is a hard error rather than a silent degrade into a global sweep. - Replica-aware leases (wy-jpd3.7, ignored migration 0016).
leases.granted_noderides the JSONL
interchange so an imported lease keeps its provenance, and--any-replicais the escape hatch for a
replica that is permanently gone. Deliberately no hostname fallback: on a shareddolt sql-server
many hosts are clients of one store, and a hostname guard there would stop a supervisor reaping any
worker's lease at all. Documented limitation: a heartbeat proves the holder is alive, not that the
lease moved, sogranted_nodeis backfilled but never overwritten — a renamed replica keeps reading
foreign and is recoverable only with--any-replica. bd sync— the federation loop as one verb (wy-jpd3.4), andbd conflicts list|show|resolve
(wy-jpd3.5) as the companion that clears an exit-2 halt.bd conflicts showrenders each conflicted
row field by field (only disagreeing fields unless--all-fields);bd conflicts resolvetakes
named beads row by row or whole tables with--all, via--ours/--theirs/--strategy, then
concludes the merge. Notebd conflictsis not supported in proxied-server mode.- Compare-and-set updates:
bd update --if-assignee/--if-status(bd-wsqvw, epic wy-mdi5h). See
Highlights. Guards require a field update to ride on, are mutually exclusive with--claim(its own
CAS), and compose with each other and with the engine'sExpectedVersionrow CAS. Library consumers
get them through newUpdateIssueOptions.ExpectedAssignee/ExpectedStatuson the existing
UpdateIssueChecked— no interface change. An out-of-treeStorageimplementation that ignores the
new fields simply does not enforce them, and should add support before advertising guard semantics. - Pool-aware claiming via the
claim.poolsconfig key (bd-bguz6). Off by default; with no
claim.poolsconfigured, behavior is unchanged. One gotcha: if a pool take's lease expires,
bd reclaimreturns the bead to the unassigned pool, not to the pool alias it was dispatched to. - A durable events journal, and
bd events tail|export|prune(bd-opisf). See Highlights. HTTP
twins areGET /v0/beads/eventsand a streamingGET /v0/beads/events:watch. Not journaled, by
design:bd dolt pull, merge-settled changes, rawbd sqlDML, store-open migrations and compaction
rewrites. Size the retention floors for the longest outage a consumer must survive — they are a
recent-window guarantee, not a consumer watermark. Reference:
docs/reference/events-journal.md. - A public Go API: the
backendandissueopspackages plus a conformance suite (bd-h3dib.2,
#4415, bd-yby99, #4911, facade waves 1–4). See Highlights. Note thebeads.Storageinterface gained
required methods across this arc (IssueClaimer(),IssueReader(),ReadyClaimer(),
BatchCloser(),DependencyEditor(),Commenter(),IssueRelations(),UpdateIssueChecked,
MergeMetadata) — callers are unaffected, but any external type that implements it must add them
to compile. --briefonbd list/bd readyand--brief-depsonbd show(#5546, #5547, #5549, #5554,
#5586), withbrief/brief_depson the HTTP twins. See Highlights for the measurements. Refused
where it could not be honored: onbd readyit requires--jsonand is refused with--claim,
--gated,--moland--explain; onbd listit works in text mode but is refused with
--watch, the--parenttree walk, and--format, where a caller's template could print a dropped
field with nothing to mark it. Text mode carries the single visible marker:bd list --long --brief
printsDescription: (omitted by --brief).--max-rowson the walking reads, plusBEADS_MAX_ROWS. A hard upper bound on rows returned by
bd list,bd ready,bd graph,bd dep treeandbd find-duplicates, exiting 2 when exceeded; 0
disables. Honored on both the direct and the proxied-server route. A circuit breaker for CI and agent
rigs against one bad filter returning a multi-hundred-megabyte result, settable fleet-wide by
environment.bd provenance record|log|by-ref— an append-only provenance event log (#4461). Records typed
bindings from a bead to an opaque external artifact:--kind(cut, claim, suspend, resume, handoff,
commit, land, used),--source,--refwith--ref-kind(git-sha, pr, work-id, transcript,
branch). Append-only — no update, no delete — and idempotent on a deterministic id computed from
source:issue:kind:(ref or --at), so a git hook or CI job that fires twice is harmless. beads never
interprets the actor or the ref; only kind and ref-kind are structurally validated. This is the
supported way to bind beads to commits, PRs and transcripts without abusing labels or metadata.bd schema— a published JSON Schema for--jsonand export output (#5098). Reflected from the
same Go structs bd actually serializes, so it cannot drift from the real output; named string enums
carry their allowed values and referenced types are inlined, so each record schema is self-contained
and directly consumable by codegen (bd schema | jq '.types.issue'). Runs without a workspace or
database. Reference:
docs/reference/json-schema.md.- Cursor agent hooks, and Cursor at parity with Claude Code and Codex.
bd setup cursorinstalls
.cursor/hooks.jsonalongside the rules file, wiring three lifecycle events to a hidden
bd cursor-hook:sessionStartinjects fullbd primecontext into every new agent session,
preCompactarms a one-shot refresh marker, andpostToolUsere-injectsbd primeexactly once
after a compaction then no-ops — so beads context survives compaction instead of being forgotten
mid-session. Existing user hooks are preserved and--removeonly removes the beads-managed entries.
Parity work rides along:bd initauto-installs Cursor the way it already does Claude Code and
Codex;--globalwrites~/.cursor/hooks.jsonand the agent skill to~/.agents/skills/beads;
.cursor/rules/beads.mdcnow wraps the sharedrecipes.Templateinstead of a hand-maintained copy
that drifted; andbd doctorreportsCursor Integration,Cursor Settings HealthandCursor Hook Completeness. Verified on the Cursor 2026.06 line; early-2026 CLI builds only fired shell hooks. - Vendor-neutral credential resolution for a protected or gateway database. A new credential seam
resolves what bd uses to open a protected database at command time, backend- and issuer-neutral: a
source yields either a secret that lands in the password slot of a direct connection, or an identity
presented as the connection username to an authenticating gateway, and an ordered ladder takes the
first configured hit and fails closed when a configured source errors. The built-in rung is the
standard credential-process idiom (kubectlExecCredential, AWScredential_process, git credential
helper): the command's stdout is a short-lived token, bare or in a{token,expirationTimestamp}/
{access_token,expires_in}envelope. Configured viaBEADS_DOLT_CREDENTIAL_COMMAND— environment
only, deliberately notmetadata.json, because a metadata-sourced command is arbitrary code run on
open. The credential's kind is decided by the config slot that produced it and never inferred from
the value, so an identity token can never be mistaken for a password. bd migrate --force, and automatic fast-forward when a remote-ahead adopt is provably loss-free
(#4259, #4516).bd migrate --force(andbd migrate schema --force) is the CLI twin of
BD_ALLOW_REMOTE_MIGRATE=1for the single designated migrator, and it is process-local so it cannot
leak into child processes (git hooks, dolt subprocesses) the way an exported variable does.
Separately, when the smart gate finds the remote ahead with no content skew, this clone's local Dolt
history a strict ancestor of the remote's with a clean working set, and the fast-forward would
land exactly at this binary's own latest migration, bd fast-forwards automatically rather than
stopping with an adopt directive — nothing local is discarded. An unpushed local commit, a dirty
working set, or a remote not exactly at this binary's latest migration all disqualify it and fall
back to the manual adopt directive, never a forced write.BD_SMART_GATE=0opts out. Directly
relevant on upgrade day, when every clone is behind at once.bd dolt clean-databases --purge-dropped, including in proxied-server mode (be-pq5, #3663,
p1-9lf).DROP DATABASEonly moves a database's directory under.dolt_dropped_databases/; Dolt
keeps the data there — recoverable viaCALL DOLT_UNDROP(name)— until an explicit purge, so disk
usage on a shared server stayed high across repeated cleanup runs. The flag runs that purge, and
fires even on a run that finds nothing stale, since a prior run's residue is not visible to
SHOW DATABASES. Read the warning as written:CALL DOLT_PURGE_DROPPED_DATABASES()is
server-global and irreversible — Dolt cannot scope it to the databases a given run dropped, so this
also permanently deletes every other dropped-but-unpurged database on the server and removes
DOLT_UNDROPrecovery for all of them. It defaults to off for exactly that reason.- Deployment-mode migration commands, and
bd migrate legacy-sqlite. Fourbd migratemode-switch
subcommands move a workspace between Dolt deployment modes in place —from-server-to-proxied-server,
from-proxied-server-to-server, and the two shared-server variants — all marked experimental.
Both proxied-server and server mode root theirdolt sql-serverat the same.beads/doltdirectory,
so the switch is a config change rather than a data move. This matters becausebd serverefuses
embedded Dolt: these are the supported route from the default workspace mode to one that can serve
HTTP.bd migrate legacy-sqlite --source-db PATH --output PATH|-reads an authenticated legacy SQLite
database read-only and emits JSONL.bd migrate-personalmoves your own planning beads out of a
project database into your personal planning repo. - New global flags:
--no-color,--database,--mem-profile.--no-coloralso honors
NO_COLOR=1/CLICOLOR=0.--databaseruns a single invocation against a different server
database without changing the project's configured database (proxied-server mode only) — and note the
paired semantic shift: in proxied-server mode a--dbvalue that is not an existing path is now
treated as a database-name override.--mem-profile FILEwrites a heap profile on exit (also
BEADS_MEM_PROFILE). - New
bd initdeployment flags, all experimental where marked:--team-server(the shared
database's schema is managed externally; bd never creates the database or runs migrations, only
verifies the schema version — proxied-server mode only),--server-tls(require TLS for the
init-time Dolt server connection; not persisted, so set the environment or credentials for
subsequent commands),--proxied-server-port,--proxied-server-idle-timeout(default 30s; 0 keeps
the proxy alive indefinitely), and the--proxied-server-external-tls-*trio. - Assorted new flags across existing commands.
bd create --storage-class {versioned|unversioned|ephemeral},--status,--allow-empty-description;bd list --deps[=scheduling|all],--external-ref,--external-contains;bd ready --label-regex,
--label-pattern;bd graph --open;bd status --no-blocked(not supported in proxied-server
mode);bd export --exclude-owner(repeatable; also readsexport.exclude_owners) and--verbose;
bd prime --no-memories,--max-memories,--max-memory-chars— agent context budget is now
tunable;bd types --sections;bd history --events;bd prune --ignore-references;bd q --parent;bd human respond --file/--stdin;bd worktree remove --merged-into;bd dolt pull --strategy;bd dolt remote reset-data(the recovery step after a history squash, where a plain
push cannot advance a rewritten history); andbd formula schema, which prints every exported struct
a.formula.toml/.formula.jsoncan declare, generated from source so it cannot drift.
New in [1.3.0]
- The events journal records WHO performed each mutation.
bd_events_journalgains anactor
column (migration 0066 plus its ignored-series twin 0025), stamped inside the mutating transaction
with the same identity the audit-events table resolves.bd events tail/bd events exportJSON
gains an additive, omit-when-emptyactorfield — empty means the path had no actor, never a user.
Delete and cascadedep_removerows carry the request actor too, and reading a disabled journal now
says so instead of returning silence (#6061, #5985). sortonGET /v0/beads/issues(#5666). Two values, and the set is closed:created(the
existing order, now spellable) andpriority(bd list's flagless ordering) — measured over a
1400-row store at the 200-row page an HTTP client actually uses,?sort=priorityturns a 7-request
page-and-resort walk into 1 request. Cursors are nowv2tokens that carry their order, and a cursor
replayed under a differentsortis refused asinvalid_cursorinstead of silently paging a
different total order; outstandingv1tokens stay readable, so no traversal in flight has to
restart. Absentsortstill meanscreated, permanently.bdwarns when it stores a label containing a space (#5813).-l 'good first issue'is stored
as asked, but a missed comma looks identical at the point of writing, so a stderr line catches it at
the keystroke:⚠ Stored "auth backend" as ONE label — it contains a space.Advice, not an error;
--quietsilences it.bd doctorgains a matching warn-onlyLabel Whitespacecheck, andbd doctor --fixrepairs damage already in the database.bd staleandbd blockedgain--label,--label-anyand--exclude-label(#5822), with the
same meanings they already have onbd listandbd ready. Filtering happens in SQL ahead of
LIMIT, so--label x --limit 10returns ten matching beads.- New
*.gate.lockfiles appear next to and inside.beads(#5046, #5093). A two-level
cooperative flock gate serializes operations that cannot safely overlap — ordinary commands take it
shared, maintenance likebd backup restoretakes it exclusive. The files are the lock's name, not
its state: created once and deliberately never deleted. Both are covered by the*.gate.lock*
gitignore patternbd doctormaintains. - A wisp-plane upgrade corpus (#6049). Neither upgrade harness had ever seeded a wisp, so the
clone-local plane the 1.3.0 chain rewrites —wisps,wisp_dependencies,wisp_comments,leases,
events, and the ignored-migration cursor — used to reach the candidate binary empty. A new lane
seeds those classes and asserts them across the upgrade, including detection of an ignored-track
replay the old harness was structurally blind to.
Changed
First shipped on a tested release here — the withheld [1.2.1] work
bd searchincludes closed issues by default (bd-t5yex). The dominant real-world query is "was
this already found/filed/fixed?", exactly the query where silently excluding closed beads produced a
false "no" — downstream repos had grown shell wrappers purely to undo the old default.bd list
keeps its open-only default, andbd query(plus the HTTPq=endpoint) deliberately keeps its
closed-exclusion default with opt-in--all. Applies to both the embedded and proxied-server paths.- Cross-type blocking dependencies are now allowed (bd-wg7ve, #4034, supersedes GH#1495).
bd dep add <task> <epic>— gating a work item on an epic completing — used to fail with a
backwards-reading error. The blanket same-type rule is replaced by a hierarchy deadlock guard that
rejects only the cases that actually wedge the graph: gating a bead on its own ancestor (which
cannot close until its descendants finish) or on its own descendant (blocked status cascades down
to the very bead that must close to clear the gate). Sibling ordering edges stay allowed, and the
guard now coversconditional-blocks, which previously skipped cross-type validation entirely. A
task gated on an epic becomes ready when the epic closes. Over HTTP the refusal is
409 dependency_cyclecarryingissue_id,blocker_idandblocker_is_ancestor; the absence of
those members is what tells a client it got a plain scheduling cycle instead. bd deleteis one transactional role across every route. Existence probe, dependents guard,
cascade expansion, deletion and the[deleted:ID]reference rewrite now run as one transaction on a
shared role used by the direct route, the proxied route andbd serve. Consequences beyond the
team-server break listed above:bd delete --cascadeon a wisp no longer marks live beads as
deleted in other beads' text (the rewrite set was computed differently from the delete set, and
bd wisp gcran exactly this path), a one-id delete without--forcerefuses when the bead has an
outside dependent instead of printing a preview and exiting 0, and the whole request is refused when
any id names no bead. Know the trade: the transaction is now as large as the deletion plus its graph
neighbourhood, so a delete whose neighbourhood exceeds the backend write timeout fails whole
rather than deleting rows and leaving stale text — split very large--from-filebatches. Text
already corrupted by earlier builds is not repaired.bd purgeandbd pruneselect and delete in one transaction (bd-pn231). The count a sweep
reports is now the set it deleted; previously a bead closed between the two transactions could be
counted and not deleted, or deleted and not counted. Same trade as above: the Dolt server backend can
no longer batch wisp deletions 200 at a time, so a purge big enough to exceed Dolt's write timeout
now fails whole instead of deleting part of the workspace and reporting nothing — sweep in slices
with--patternor--closed-before. Over HTTP,POST /v0/beads/issues:sweepdefaults
protect_referencedto true where the CLI's--ignore-referencesis opt-out, on purpose: being
authenticated says nothing about whether this particular deletion was meant.bd create --fileis all-or-nothing, and its plan-wide flags now mean something (bd-lu170). Both
routes build one batch request on a shared role, so a markdown plan is created as one act with one
history entry on either. A file the workspace refuses now creates nothing — each template goes
through the same validation a singlebd creategoes through — and a declared dependency on an id
that does not exist is an error rather than a silently dropped edge, which is how a plan file with a
typo used to produce a graph that looked complete and was not.--ephemeral,--no-history,
--mol-typeand--validateare now honoured on the direct route, where all four were accepted and
silently ignored while the proxied route acted on them. Re-running a file that used to fail can
give a different outcome, because the earlier run may have landed rows this one refuses to
duplicate — checkbd listbefore re-running a plan that errored on an older version.bd create --graphis untouched.bd serveruns on a registered storage backend, andcontextstops guessing.bd servealone
used to reimplement a creation path hard-wired to Dolt's SQL wire; it now uses the store the root
command already opened, through the same registry dispatch — one creation path rather than two, with
the startup line naming both source and backend. Relatedly, the shared identity projection behind
GET /v0/beads/contextandbd contexthardcodedbackend: doltand copieddolt_modeand
databaseunconditionally, so a registered-backend workspace was confidently reported as
backend="dolt",dolt_mode="embedded",database="beads"on the one endpoint automation is told to
trust for a server's identity. The backend is now named as the store open resolves it, which also
closes a latentCGO_ENABLED=0bug where a registered workspace was handed to the Dolt provider and
either failed with a misleading error or connected to a defaulted host and served the wrong
database.- Field-level three-way auto-merge on pull. beads stamps
issues.updated_aton every mutation, so
any two edits to the same bead on two replicas between syncs collide on that cell even when the
semantic fields are disjoint — machine A adds a comment, machine B adds a label, and the row conflicts
on nothing but the timestamp both bumped. The observed conflict rate was therefore far higher than the
semantic-conflict rate, and the row-level last-write-wins resolver could only take the safe half of
it: it declined whenever both sides had movedupdated_atpast the merge base, because taking one
side's whole row would drop the other side's field edits. A field-level three-way merge now encodes
beads' actual write semantics — a column only one side changed relative to the merge base keeps that
side's value, and only a genuinely contested cell falls to last-write-wins. Reached through
bd dolt pullandbd sync; there is no flag. bd label add/removeover many beads is no longer one atomic transaction (ga-26w10, #5489).
Both routes now end at the shared lifecycle and reader roles, deleting the four-way
(add|remove) × (wisp|durable)switch the proxied route hand-rolled below them. The deliberate
delta: a multi-bead edit is N guarded updates rather than one raw transaction, so it records N history
entries and a failure partway leaves earlier edits written. Neither shape is obviously right — the new
one attributes each edit to its own bead and lands what it could — but a script that depended on
all-or-nothing label edits needs to know the guarantee changed.bd label list-alland
bd label propagateare unchanged and deliberately stay off the role.- Text-input commands refuse two sources, and
bd humanmatchesbd list(#5332). See the breaking
list forbd comment/bd note/bd comments addand--status=all. Alongside them:bd human respondgains the same input layer (--responseis no longer required) andbd human dismisstakes
its reason positionally;bd human listmatchesbd list's status handling while hiding no bead
type, because ahumanlabel is an explicit request for a person's attention; andbd human stats
classifies dismissals by theDismissedclose-reason prefix shared as one constant with
bd human dismiss, instead of matching the substringdismissanywhere in the reason. Beads closed
before this with a lowercase or mid-string "dismiss" move from the Dismissed count to the Responded
count. --dolt-auto-commit batch/offactually defer version commits in SQL-server mode (bd-4wamg). The
mode was silently inert there: the CLI's policy was embedded-only and the storage layer minted one
Dolt commit inside every write transaction regardless — measured at one commit per write through all
five configuration paths. The write verbs now thread the deferral to the storage layer's commit sites
in server and embedded mode alike. The server-mode default changes spelling, not behavior: it used
to resolve tooffwhile behaving likeon, and now resolves toon, naming what a default
server-mode write has always done. On a shared server, staging is table-level, so anon-mode writer's
commit may sweep up another client's deferred rows — deferral bounds who creates commits, not commit
contents.- The PostgreSQL and MySQL adapters were rolled back before entering a tagged release (bd-sadcd).
The rationale is worth stating because it is the shape of the storage story: dialect, credential,
schema-lifecycle, migration, CI and operational complexity, against a project whose version-control
semantics are Dolt's. An existing PostgreSQL or MySQL workspace stops before its configured
database is opened or modified. See the Upgrading note above for what the supported storage paths
actually are at this tag.
New in [1.3.0]
- Shared stores refuse version-bump migration without explicit consent (#5920, #6048, #6055, #6088;
closes the gate leg of #5043). Embedded and single-user databases still auto-migrate silently, and
fresh databases still migrate on creation — creating a database is consent for its schema. But a
shareddolt sql-serveraccepts co-resident clients by construction, so a version bump there refuses
instead of taking the whole fleet's schema forward as a side effect of one upgraded client; the smart
gate's "safe first-mover" and auto-fast-forward arms are embedded-only for the same reason (#4516).
The gate now also runs on the proxiedbd serveopen path, which had been migrating shared databases
on every open — including from read commands — since v1.2.2: read commands warn and continue, writes
refuse,bd servefails to start until the schema is reconciled,bd migrate schemaworks in proxied
mode instead of refusing, and gate failures print their full actionable block instead of one truncated
line. "Shared" is drawn deliberately (#6088): shared-server mode, an operator-managed server, a
remote host, a TLS endpoint, a unix socket, or no workspace at all — a server bd auto-starts for a
single workspace is a database no other client can observe, and migrates on open as it always has. bd doctorrespects--readonlyand the migration freeze (#6056; fixes #6028).bd doctor --fix
previously mutated workspaces that strict--readonlyand an active freeze had both declared
off-limits — and plainbd doctor, with no--fixat all, could rewrite.local_versionor
auto-migrate a frozen store as a side effect of diagnosis. Both bypasses are closed, with no
doctor-side override, and both gates key on the directory doctor was pointed at rather than only the
one it was launched in.- Write commands refuse to run while a
MIGRATION-FREEZEmarker is present (dc-6jaq, #6043).
Discovery is generic: the marker is found in the workspace directory, the working directory, or any
ancestor of either, andBD_MIGRATION_FREEZE_FILEis authoritative when set. Every write command
(~120 call sites,bd qincluded), plusbd init --reinit-localandbd bootstrap, prints
⛔ workspace is frozen for migrationnaming the marker's own path and exits 14
(ExitMigrationFrozen) instead of a generic 1. Read commands keep working, and bd's own maintenance
stands down with them — auto-migration, JSONL auto-import, Dolt auto-commit, auto-backup, auto-export
and auto-push are all skipped, so a frozen store is never rewritten just because someone ran a read.
A runningbd serveis not gated: stop it before freezing. - Migration 0061 rekeys every events, comments, snapshot and compaction-snapshot row to a
content-derived id (#5150). Random UUIDv7 ids never converged under newest-wins replication; ids are
now UUIDv5 over the row's own content, so the same fact derives the same id everywhere. Existing rows
are converted by a one-time, crash-resumable bulk rekey on the first open after upgrade — the main
reason that invocation is slower. Any external reference to an event, comment or snapshot id taken
before the upgrade will not resolve afterwards. - The
eventsaudit table is now clone-local (#5162). Migration 0062 moveseventsonto the
dolt_ignored plane, preserving every row. Events keep full SQL durability locally and
bd history <id> --eventsstill reads them, buteventsrows no longer replicate — minting a
versioned Dolt commit per audit row was the dominant source of commit churn on a busy store. Comments
stay versioned. - The
interactions.jsonlaudit sidecar is opt-in (#4688).audit.enableddefaults to false and
bd initno longer creates the file; the database-backed replacement isbd history <id> --events. - Auto-backup defaults to off under a Dolt sql-server (wy-zrmqr) — on one shared server, ~31 clients
had each independently decided to back up the same database. Embedded mode is unchanged; an explicit
backup.enabledalways wins, andbd config get backup.enablednow prints the effective value with
its source. --profileis now--cpu-profile, with no alias (#5126). The old spelling fails as an unknown
flag instead of silently doing nothing.bd importrefuses a redirected stdin rather than quietly ignoring it (#5171).
bd import < file.jsonllooked likebd import -and behaved like barebd import; it now errors
and names both fixes.bd hooks install --chainand--forceare accepted no-ops (#5284). Managed marker sections
always preserve content outside them and always keep an existing hook running alongside the bd
section; symlinked and git-tracked hook paths are refused before anything is written.- An explicitly configured Dolt server port now outranks the ambient
BEADS_DOLT_SERVER_PORT/BEADS_DOLT_PORTenvironment variables (GH#4052). A port asserted by the
user —bd init --server-port, config, a library caller — is no longer silently replaced by whatever
the surrounding shell exported, and the legacyBEADS_DOLT_PORTspelling no longer overrides
configured ports. Ports bd resolved for itself stay overridable by either spelling, and a stale
.beads/dolt-server.portleft by a crashed server no longer makes auto-start refuse over a port
nobody configured. - The legacy-backend tombstone rejection is truthful (#6044). A workspace whose
metadata.json
still says"backend": "sqlite"(v1.2.x opened these as Dolt anyway) is still refused fail-closed,
but when live Dolt data is detected the message now carries the exact one-field heal instead of
advising an export/reinit that would have destroyed a database one edit from healthy. Detection helper
ported from @steveyegge's #4740. - The new exported Go packages are marked experimental before the tag freezes them (#6036).
backend,beadserrors,issueops,journalops,memoryops,schemaand the conformance profiles
ship usable but explicitly unfrozen — the extension door for out-of-tree backends stays open without a
v2 to walk back through. Pin an exact beads version and re-run the conformance suite on every bump. bd showlabelscreated_byasCreated by:, notOwner:(be-ss66);bd dep addnames the
implicittype=blocksdefault to interactive operators (#5854);bd reclaimsummarizes replica-guard
skips in one line instead of one line per stranded lease (wy-sp2l4);beads_dirandrepo_root
become OPTIONAL inContextResponse(bd servestill publishes both — the relaxation is a promise to
clients, not a switch on the server); and actor matching decodes an exact--run to/(be-p7dzx).
Fixed
First shipped on a tested release here — the withheld [1.2.1] work
- A dated defer now means what it says: expired defers return to the ready front (bd-i8qx8).
bd defer --untilandbd update --defersetstatus=deferredplus adefer_untiltimestamp, but
nothing ever flipped the status back — so an expired defer stayed invisible tobd readyforever
until a human ranbd undefer. One deployment measured 241 beads, including P1s, silently dark this
way, regenerating daily from correct-looking automation. Ready-front reads and claims (bd ready,
bd ready --claim,bd list --ready, and the serve/proxied reader roles) now run a lazy wake sweep
first: every bead and wisp withstatus=deferredanddefer_until <= nowflips to
status=open, defer_until=NULL, byte-identical to whatbd undeferwrites so a later dateless
re-defer cannot inherit a stale past date. Each wake records astatus_changedevent with actor
bd-defer-wake. What does not change: a dateless defer is the indefinite icebox and never
auto-wakes. The sweep is a no-op when nothing has expired (no Dolt commit is minted), advisory by
contract (a ready listing never fails because the sweep could not run; strict--readonlyskips it),
and identical in embedded, server and proxied-server modes. --label-anyis no longer silently dropped bybd readyandbd ready --claim(bd-s10oa). The
ready-work WHERE builder emitted clauses for--labeland--exclude-labelbut none for
--label-any, so the OR-set filter was ignored on the ready and claim paths on every backend — while
bd listandbd searchhonored it. On an atomic claim this was dangerous rather than merely
wrong: a worker fencing itself to its own lane would claim another lane's bead and believe it was
fenced. An exhausted lane now claims nothing instead of falling back to unfenced work. Related:bd ready --claimnow honours the directory-label scope, where the branch applying a directory's
configureddirectory.labelsdefault tested the filter it had already written the default into, so it
could never fire.bd queryno longer drops matches from anORorNOTquery (bd-pohmh). To answer an expression
the storage filter cannot express, both routes fetchedmax(3 × --limit, 100)rows and applied the
expression to those in memory. A match past that window was absent from the page — and the truncation
hint said nothing, so the answer looked complete:bd query "type=bug OR label=urgent"over a
workspace with more than a hundred beads has been returning an arbitrary prefix of its answer. The
window is gone; the page is the first--limitmatches and the truncation hint is exact.
--offset Nnow works withOR/NOTwhere it used to be refused outright. Your results will
change, and they change by getting bigger. The cost is real: a broadOR/NOTexpression over a
large workspace now reads every row its plain filters admit — narrow the expression if that matters.- Claim-family writes are verified by re-read in Dolt server mode (bd-zccb9, incident wy-ejph3).
Under a degraded sql-server the exit status ofbd update --claim/bd claim/ unclaim was not truth
in either direction: a claim could report success while the server-side transaction died with the
abandoned connection and rolled back — a phantom claim that later cost a duplicate implementation —
and conversely a connection error could print with the write actually applied. bd now re-reads the
bead's assignee and status on a fresh connection after the claim transaction and resolves the
outcome against the database: a reported success that did not land fails loudly, and an ambiguous
commit-phase loss is settled by the re-read (verified applied becomes an accurate success; verified
rolled back is replayed once). New metricsbd.claim_verify_lost_totaland
bd.claim_verify_recovered_total. Applies to claim, ready-claim and unclaim in server mode; wisps and
embedded mode are unchanged. - Script hooks now fire on both write plumbings, and a command waits for its own hooks (bd-opisf).
bd has two write plumbings and only one ran the workspace's hook scripts. The storage decorator chain
fireson_create/on_update/on_closeafter every mutation it lands; the unit-of-work plumbing —
the one proxied-server mode writes through — fired nothing, so an integration wired to
.beads/hooks/silently missed every mutation that went through it. Four commands had grown
hand-wired hook calls to paper over the gap; every other write ran no hook at all. A notifying wrapper
now fires them from the plumbing, buffered during the transaction and drained only after the commit
succeeds. If you run a team server and wired anything to.beads/hooks/, it has been silently
missing most mutations and will start running on upgrade. One change every workspace sees: a command
now waits for its own fire-and-forget hooks before exiting (bounded by the 10s per-hook timeout),
because a short command could previously return frommainbefore a hook had even started; the wait
happens after the store closes, so a hook script's ownbdcan open the workspace.bd servestill
runs no hooks, andbd importstill fires none on either plumbing. - Team-server data corruption: proxied
bd init,bd create --id, andbd update --ephemeral
(bd-zl3u8, bd-7oyh5, bd-xt6de). Three independent silent-corruption bugs on the--proxied-server
route. (1)bd init --proxied-serveragainst a team server whose database another rig had already
identified wrote its own locally-derived issue prefix and project id straight over the stored pair —
renaming every id the co-tenant was about to mint. It now reads the identity first and prints
Adopted project identity from existing database; there is no flag that restores the overwrite.
(2)bd create --id <id>on a proxied server silently upserted every column of a resident bead —
title, type, description, labels — and exited zero, with no history entry naming a create and nothing
in the output to suggest anything had been replaced; both routes now return an already-exists error
naming the id and leave the stored row untouched. (3) Proxiedbd update --ephemeral/--no-history
ran a plain column update on whichever table the row already sat in, so the bead stayed inissues
carryingephemeral = 1— still durable, still versioned, still replicated, but invisible to every
wisp-plane read and skipped by JSONL export; both routes now perform the atomic aggregate move.
Caveat: the--ephemeralfix does not repair existing rows. Any bead a proxied
bd update --ephemeralproduced before this release is still inissueswith the flag set, and
nothing here finds it. - More team-server parity, from the facade programme. Proxied
bd config set status.customand
types.customnow project into their tables so the setting and its effect land together (previously
bd config set types.custom "session"reported success andbd create -t sessionfailed for as long
as the workspace lived);bd config set-many issue_prefix=<x>is refused asbd config setalways
was, where it used to walk past the guard and re-prefix the workspace; proxiedbd reopenreopens a
bead in any configured done status;bd update --parenton a proxied server replaces every parent
edge instead of stopping at the first;bd dep removeon a wisp-sourced edge actually removes it; a
dependency on a bead in another repository no longer fails with a raw foreign-keyError 1452;
bd dep treeon a team server resolves partial ids and honors--max-rows; and proxied
bd create/reopen/update --jsonemit the direct route's shape, gaining thelabelsfield they
were silently dropping. bd purge --pattern/bd prune --patternstop reporting success on a malformed glob, and
--dry-runmatches the real run (bd-pn231). Both routes matched with the error return of
filepath.Matchdiscarded, sobd prune --pattern '[' --forceprintedNo closed beads to pruneand
exited 0 — indistinguishable from a correct pattern over an empty set, which is the worst possible
failure mode for a cleanup script. And--dry-runasked for the refuse-if-any-external-dependent path
while the confirmed run asked for the force path, so a dry run could reporthas dependents not in deletion setfor a sweep--forcefinishes, with the CLI then swallowing the error and printing
zeros.bd dep cyclesis deterministic and no longer shortens a cycle it cannot fully describe
(bd-wfkbv). The walk iterated a Go map, so two runs against an unchanged database disagreed on both
cycle order and each cycle's starting point — and on a graph with overlapping cycles even the set of
cycles could differ. Members that could not be looked up were silently dropped from the path, so a
three-node cycle rendered as a two-node one and a cycle none of whose members resolved vanished from
the report and from theFound N dependency cyclescount entirely. Diffing two cycle snapshots is now
meaningful, which it never was. The same sweep backs the post-add cycle warning thatbd dep add,
bd dep --blocksandbd linkprint, on both routes. See the breaking list for the--jsonshape
change.- Telemetry no longer taxes every
bdinvocation. Two startup costs paid on every command are gone.
The machine-scoped distinct ID was recomputed on every invocation — a fork of the platform machine-id
probe (ioregon macOS, measured at 20.2±1.2ms) — even withBD_DISABLE_METRICS=1and even for
bd --version; it is now resolved only when metrics are enabled and cached at~/.beads/machine-id
(0600), with a probe failure retried next run rather than cached. Second, every invocation
unconditionally spawned a detachedbd send-metricschild — a full re-exec of the binary plus an
HTTPS upload attempt, with no check that anything was queued; the spawn now requires at least one
queued event batch and is throttled to one attempt per 5 minutes. Everybdcall gets measurably
faster, including calls from users who had already opted out of telemetry. Telemetry content,
opt-out semantics and endpoint pinning are unchanged. - A long or multi-paragraph close reason renders as body text in
bd show(#5595). Every other
free-text field reaches the terminal through the markdown renderer, which word wraps and indents; the
close reason was formatted into the metadata block besideOwner:andCreated:, so it never wrapped
and its second and later lines read as separate metadata entries — a blank line and a bare- bullet
sitting directly underCreated:.bd close --reason-fileexists so agents can write structured close
reports, and those were exactly the reasons that came out corrupted. Anything larger than one metadata
line now gets aCLOSE REASONsection rendered by the same call the other body fields make; the JSON
payload is untouched. - Smaller listing and reporting corrections.
bd listandbd childrenprint blocker ids in
ascending order with repeats collapsed, on both routes, where they used to come out in
map-iteration order so an unchanged workspace could print different bytes on two consecutive runs —
which makes diff-based snapshot scripts stable for the first time.bd list --max-rowsreports its own
malformed value before the filter's.bd status --assignedreports the underlying error instead of a
causeless "failed to get assigned statistics".bd countis one implementation again on a shared role,
with byte-identical output. Multi-idbd dep listno longer changes its JSON shape on failure, reports
ids that name nothing on stderr, answers a repeated anchor once, and no longer prints an empty section
header under--type. Claim/unclaim refusals steer toward coordinating with the holder instead of
suggestingbd unclaim <id>— the old copy taught an unclaim-then-claim steamroller that evicted a
live, heartbeated claim mid-review (bd-at6rc, #4675). And a single-beadbd create --id P.Nno longer
leaveschild_countersstale, which could re-mint an already-used suffix once such children were
archived (GH#4750). - FreeBSD builds compile again (#5661; CI gap tracked as GH#5662). The dbproxy process-identity arc
shipped linux/darwin/windows implementations with no fallback, breakingGOOS=freebsdcompilation —
caught only by the release build's cross-compile, which is what burned the v1.2.0 tag pre-publish.
Unsupported platforms now get stubs that fail proxied-mode spawns with a clean, actionable error;
classic (non-proxied) bd is unaffected, matching what v1.1.2 shipped on freebsd.
New in [1.3.0]
- Convergent dependency rekey (#6045; fixes #5268). A database can legally hold the same logical
edge twice under different typed columns; the rekey derived the same UUIDv5 for both, died on the
duplicate primary key, and — because the cursor commits per step — left the store half-applied. The
planner now orders convergible states and merges duplicate typed edges, the rename path that minted
them is fixed at the source, databases left half-rekeyed by earlier binaries are repaired on the next
pass, and the refusals that remain can no longer brick an open. - Legacy tracked
ignored_schema_migrationsis untracked at open (#6046; fixes #4356). On legacy
lineages the clone-local migration cursor predated itsdolt_ignorepattern and sat committed at
HEAD, so every migration pass dirtied a tracked table bd could never clean — push worked and every
bd dolt pulldied withcannot merge with uncommitted changes, permanently (reported independently
on two fleets, 24 and 17 databases). An open-time reconcile untracks it and unwedges the pull. bd deleteno longer wedges auto-export forever (#6059, completing #5806; fixes #5896).
Auto-export's orphan guard refused to overwrite anissues.jsonlholding a deleted bead's record,
refused again on every subsequent command, and the documented recovery re-imported the JSONL —
resurrecting the bead the user deleted. Deletions are now proven via store history (a new
storage.HistoryPresencecapability that the default embedded store implements too) and the export
proceeds; unprovable cases still refuse.- Ignored-cursor sentinel replay is clamped to floor 11 (#6054; #5981 class, #5366 follow-up). A
missing clone-local sentinel used to zero the migration cursor and replay the wholeignoredchain
from 0001 on every upgrade from a released tree — v1.1.0/v1.1.2/v1.2.x all top that cursor at 11 and
have noleasestable, so the sentinel was always missing. That replay dragged inignored/0007's
unguardedUPDATE wisps SET is_blocked = 0, silently restampingwisps.updated_atacross the plane,
unrecoverable because the wisp plane has no committed history. Upgrades now apply exactly the pending
set,ignored/0012–0026. - Server-mode
DOLT_ADD/DOLT_COMMITrun after the SQL transaction commits (#6040, carrying
@nova-submodules' #5740). Staging inside the still-open transaction materialized Dolt commits from the
BEGIN-time snapshot, silently reverting rows concurrent sessions had committed in the window —
observed in production as claims reverting minutes after being made. The commit now stages the
post-merge working set. bd bootstrapprobes git remotes for Dolt data instead of rejecting them, and exits non-zero when
it declines (#6037; fixes #5743, #5663). Bootstrap rejected the very git-origin-derived remote
bd initpersists, printed✓ Database already existsfor the refusal, and exited 0 with nothing
created — a closed loop withbd init --reinit-localpointing back at it.- Truthful errors and safe identity attribution for proxied/gateway
bd init(#6062). Nothing on
the open path asserted the SQL session was on the database bd asked for, so a credential-scoped front
door could serve its own database while bd attributed the reads to the requested one; everyUSE
failure was labeleddatabase not found, including access denials. Both are fixed, along with a
silent-data-hazard variant worse than the reported error, and--init-if-missingis honored in
proxied-server mode. --storage-classis honored on every create path (#6060). The flag was accepted and ignored on
the proxied route (minting a durable versioned row for a request that asked for ephemeral), on
--filemarkdown batches, and on--graphplans; the direct door was fixed in-window (#5149, #5164).
The ephemeral/versioned conflict is now rejected on the proxied route with the same message as the
direct one.- Three JSON-contract defects fixed before 1.3.0 froze them (#6053): revision tokens are opaque
decimal strings again on the way out and must be sent as strings inexpected_version— JSON numbers
exceeded JavaScript's 2^53 precision;--set-metadata k=5stores the typed scalar5(restoring
v1.2.2's inference); andbd create --jsontimestamps carry nanosecond precision again. - A Homebrew
--HEADversion stamp is recognized instead of being misread as0.0.0(#6079,
carrying @anisoptera's #5625; refs #5603, #5650). Version comparison scans dot-separated parts with
%dand leaves what it cannot read at 0, so every non-semver stamp bd has written compared as "older
than 0.56.0" and handed a live workspace to the pre-v56 recovery — which deletes.doltwhen the
.bd-dolt-okmarker is absent. Two stamps reach that call:HEAD-<shortsha>from
brew install --HEAD, and thev1.1.1-0.2026…Go pseudo-version that bricked five production cities
in #5650. The recovery is now gated onIsValidSemverbefore the comparison, so it can only remove
predecessors from the recovery set, never add one. Alongside it:bd doctorreports a--HEADbuild
as healthy instead of naggingbrew upgrade beads(which would undo what the user asked for), a
changed HEAD stamp counts as an upgrade so the post-upgrade reconciliation runs, and
bd upgrade status/reviewyields no delta rather than dumping the entire release history. bd flatten/bd compactrun a full GC after the rewrite (#6057; fixes #5907). Dolt's
generational GC never revisits the old generation, so the post-rewrite pass freed ~nothing and orphaned
history kept resolving, contradicting flatten's own contract.bd gc --fullis new, and a low-reclaim
pass now hints at it.bdno longer serializes every invocation behind the schema-init advisory lock (#6022). Measured
on an 18-seat rig, the lock was held 96.7% of the time and cost 0.4–2.4s of pure waiting on every
claim, heartbeat, list, comment and mail check. The steady-state probe now answers without the lock and
takesGET_LOCKonly on the path that can actually migrate; it issues no writes and fails closed.bd purgeandbd pruneselect candidates by tier (#5995). A wisp minted before theephemeral
column was set belonged to neither sweep — one production database had 858 rows no sweep could ever
reach. The tiers are now complementary predicates, so the first purge after upgrade may clear
considerably more than usual.- Server mode honors
Config.LenientOpen(#5783 by @Toady00; fixes #5781). The dirty-table
refusal's documented recovery —bd dolt commit— could not itself open a server-backed store, a
deadlock previously broken for embedded mode only. Found in the wild: 15 of 21 databases on one
deployed server carried a permanently dirtyconfigtable. - The aux-row rekey survives dolt#11131 encoding drift (#5064; fixes #4380). A drifted
events/commentstable panicked Dolt inside the re-key scan, aborting the migration and leaving the
database unopenable. Drifted tables are now skipped with a warning, recorded clone-locally in
aux_row_rekey_drifted, and re-keyed on a later pass; healing the drift itself is Dolt's
schema-encoding-drift recover-rows. This already reached 1.1.x and v1.2.2 users — it is the
[1.1.2] fix — and lands on the main line here, so a store upgrading from v1.2.2 is not newly exposed.
It is load-bearing for this upgrade specifically: the v53 → v66 chain runs a second aux-row rekey
pass through the same drift-protected code path when a v53 store crosses main-series v61, and at this
tag the dirty-table exemption is computed per-pass and scoped to exactly the tables the upcoming
rewrite will touch, so a post-marker resume cannot sweep a non-drifted table's pre-existing user edits
into the migration commit. Diagnosis and fix by @marcodelpin, carried and reworked by @maphew. - Incremental auto-export actually takes the incremental path (#5806), and three format/scope
regressions the dead code path was hiding — leaked memories, included owner issues, a missing_type
discriminator — are pinned by tests. Server-mode only; embedded mode still full-exports every cycle. - Disabling telemetry no longer strands the queued metrics backlog forever (GH#5712) — 2M+ files /
15.8GB observed on one control VM; the prune child now runs, network-free, until the backlog decays. - Quality-of-life truth-telling:
bd primesays when the memory plane could not be read instead of
impersonating an empty one (#5877);bd showon a deleted or purged bead points atbd history <id>
instead of printing text identical to an ID that never existed (ga-m6inyb);bd vc commit/
bd dolt commitsweep the entire working set and reportNothing to commithonestly; every CLI label
write normalizes its input so a label can match its own filter — one real database had 150 such rows
across 111 beads (#5813, fixes #5812);bd showno longer corrupts quoted shell globs via CommonMark
emphasis pairing (#5799); andbd blockedno longer silently ignores the label filters it already
accepted (#5822).
Security
First shipped on a tested release here — the withheld [1.2.1] work
bd dolt pushandbd syncno longer adopt a git-origin-derived Dolt remote without consent
(#5068). On a rig with no Dolt remote configured, both commands silently derived one from
git remote get-url origin, added it, persistedsync.remoteinto.beads/config.yaml, committed
that config change under the user's git identity, and uploaded the full issue history — no prompt, no
flag, no opt-out. A public git origin therefore published the whole issue database on a command the
user believed targeted an already-configured remote. Adoption is now a consent decision and fails
closed: interactively bd shows the derived URL and every side effect that follows a yes, and defaults
to no; non-interactively it refuses and exits non-zero, naming the URL it would have adopted.
--yes/-yconsents ahead of time for scripted use;--no-adoptorBD_NO_REMOTE_ADOPT=1disables
adoption entirely and wins over--yes. Workspace resolution moved below the gate, since nothing
may mutate before consent is established. Rigs with a remote already configured are unaffected.- The settings plane no longer serves the KV plane by any read (bd-rfwtv, bd-klko9).
bd kvkeys
and thebd remembermemories nested under them are user data stored as config rows — they ride in the
settings table because there is one table, not because they are settings — and both doors onto the
settings plane were listing them with their values. On the HTTP door that meant an unauthenticated
GET /v0/beads/confighanded every stored memory to anything that could reach the port, and the
surface's key-name-based redaction is no defence there because a memory's content is in the value.
The exclusion lives in the shared role both doors call, not in the HTTP handler, so the CLI and the API
cannot drift on what a setting is. Frame it honestly: this is plane hygiene, not a confidentiality
boundary —bd servestill serves every memory in full through/v0/beads/memoriesby design. - The no-ID "last touched issue" fallback is interactive-only (bd-m00pb, #4839). A scripted
bd update $ID …with an accidentally empty$IDsilently mutated whatever bead was touched last — a
real agent session corrupted an unrelated closed bead this way. The refusal now happens in argument
validation, before any store open, migration, or auto-import side effect. bd serve's bearer authentication (#5516) is listed under Added because it is new surface, but
read it as a security control: without it,bd serveis only ever a loopback developer tool, and both
flags default to today's behavior so a barebd serveis byte-for-byte the server it always was.
New in [1.3.0]
- Go toolchain 1.26.5 → 1.26.7 (#6047), closing seven stdlib advisories reachable from the shipped
bdbinary: quadraticnet/urlpath resolution,html/templateJavaScript-regexp context tracking,
unbounded post-handshakecrypto/tlsmessages, a missingReadHeaderTimeoutonnet/http's
unencrypted HTTP/2 check, unbounded recursion inencoding/xmlandencoding/asn1, and the
x/net/idnaPunycode bug innet/http's vendored copy —bd serveis a real HTTP server and bd makes
outbound TLS calls. Thegodirective stays at 1.26.5, so importers of the module keep their current
floor. - Dependency bumps clearing all 25 open advisories (#6047), one commit per cluster with a
reachability verdict each:golang.org/x/crypto0.55.0,golang.org/x/mod0.40.0,
klauspost/compress1.18.7,moby/go-archive0.3.3,kin-openapi0.144.0 withoapi-codegen2.7.1,
and a refreshedbeads-mcp/uv.lock. None of the fixed code is reachable from the shipped binary — the
headline critical iskin-openapi'sopenapi3filtermiddleware, which enters the graph only through
thetooldirective for spec codegen. No behavior change; no dolt bump was required.
Troubleshooting (upgrading from v1.2.2)
Most upgrades need no command at all — on an embedded or local store the first invocation migrates in
place. The papercuts below are the shapes worth recognizing before you file a bug:
| Symptom | Cause | Fix |
|---|---|---|
Migration counter jumps backwards to 0012 after reaching 0066 |
The clone-local series runs after the main one, with its own numbering | Let it finish — it is not a loop |
| A piped or CI upgrade prints nothing | Progress goes to stderr only when it is a terminal | Not a stall; wait for exit |
A shared dolt sql-server did not migrate at all |
It is never auto-migrated; consent is explicit (#5920, #6048) | Upgrade every client, then bd migrate schema once — or BD_ALLOW_REMOTE_MIGRATE=1 in scripted use |
bd serve refuses to start against a proxied store |
A daemon has no operator to consent for it, and the gate now covers that path (#6055) | Reconcile the schema first, or set BD_ALLOW_REMOTE_MIGRATE=1 as standing consent for the service |
bd serve refuses an embedded workspace outright |
Embedded Dolt commits outside the SQL transaction, so per-request atomicity would be a lie — this refusal is permanent | Move to a Dolt sql-server mode: bd migrate from-server-to-proxied-server and siblings (experimental) |
Another machine's bd refuses the database after one client upgraded |
Forward schema-skew guard: a mixed-version fleet is a broken fleet | Upgrade every client together; check which -a bd; designated-migrator flow for multi-clone stores |
bd dolt push / bd dolt pull refused after installing 1.3.0 |
The pending-migration gate covers sync, not just bd migrate |
Finish syncing with the old binary first, or migrate and push from the designated migrator |
bd dolt push in CI now fails asking for consent |
Git-origin remote adoption no longer happens silently (#5068) | Add --yes, or configure the Dolt remote explicitly; --no-adopt to forbid it entirely |
A long-running bd --readonly serve does not come back up |
The flag is now refused instead of binding a silently writable server | Drop --readonly from the serve invocation |
⛔ workspace is frozen for migration, exit 14 |
A MIGRATION-FREEZE sentinel in the workspace root, the cwd, or an ancestor of either |
Remove the file the message names (or unset BD_MIGRATION_FREEZE_FILE) once the migration is done |
A bd update exits 13 instead of 0 or 1 |
A --if-assignee/--if-status guard did not match: a racer won, nothing was written |
Skip gracefully — 13 means stale precondition, not infrastructure failure |
bd sync exits 2 and nothing was pushed |
A merge conflict it will not resolve by picking a side | bd conflicts list, then bd conflicts resolve <id> --ours|--theirs |
bd ready suddenly lists beads you deferred months ago |
Expired dated defers now wake back onto the ready front (bd-i8qx8) | Expected, one-time; dateless defers still never auto-wake |
A bd search script's hit count changed |
bd search now includes closed beads by default (bd-t5yex) |
Add --status open, and raise --limit when hunting live work |
bd dep cycles --json breaks a parser |
The shape is now {members:[{id,issue?}],partial} (bd-wfkbv) |
Update the parser; issue is absent, never null, for an unresolvable member |
A team-server bd delete --force left orphans behind |
--force no longer implies cascade on the proxied route (bd-x82so) |
Add --cascade where you meant the subtree |
Open refused over "backend": "sqlite" in .beads/metadata.json |
A stale field from the 1.2.x era; the live data is Dolt | Apply the exact one-field edit the rejection message gives |
First bd purge after upgrade clears far more than usual |
Legacy typed wisps are now reachable by the tier predicate | Expected, one-time (#5995) |
| A cross-rig bead gate stays pending under a proxied-server rig | Known limitation: multi-rig prefix routing (routes.jsonl) is not supported with proxied-server rigs (#5861) |
bd gate check, or bd close --force |
Validation
- The release was cut from
release/1.3.0, and every change on the branch landed through a reviewed
PR against it — 28 of them, driven by a full pre-tag release audit. - The full check matrix is green at the branch tip. The release-prep PR (#6038), whose merge commit
b3ef65c8is the tip, was tested on a head that had already merged #6088: 122 checks green, 1
skipped, 0 failing. That matrix is the Embedded Dolt Cmd shards (20), Embedded Dolt Storage shards
(5), Proxied Dolt Cmd shards (15) and Server Dolt Full Suite shards (16), plus Embedded Dolt
Conformance (core and audit), Server Dolt Conformance, the storage-backend conformance oracle, the
contract corpus, the Differential Regression against the v0.49.6 baseline, the macOS lane, the Windows
lanes (native/msys2/cygwin make shells, dbproxy server,doltversion,cmd/bdliveness,
worktree-remove boundary), andnix build .#default. - The last red lanes were closed before the prep PR, and only one was a product defect. #6088 was
that one — the #6048 consent gate had swept in bd-owned auto-started servers and re-checked itself on
every schema-init retry, red across the Server Dolt matrix — now fixed and pinned by
TestSharedServerDatabasein both directions. The other three were harness bugs: ajsonOutputglobal
leak breaking the shared-migrate-refusal subtests in full-package runs (#6073), a freeze-marker path
comparison that was not symlink-safe, which is what the macOS doctor freeze-gate failures were (#6087),
and the missing assertion for #6053's string revision token in bothshowsuites (#6086). - Upgrade smoke ran the five legs that matter for this release — v1.1.0, v1.1.2, v1.2.1, v1.2.2 and
v1.2.2-rc.1 → candidate — alongside 14 historical upgrade lanes from v0.9.1 through v1.2.2. - A new wisp-plane upgrade corpus (#6049) seeds and asserts the clone-local plane the 1.3.0 migration
chain rewrites — wisps, wisp deps and comments, leases, events, and the ignored-track cursor — across
v1.0.1/v1.1.x/v1.2.2 → candidate, closing the coverage hole behind this release's sentinel-replay and
restamp fixes. - The v53 → v66 upgrade, the shared-server consent flow, the backup-first recipes, and the multi-clone
designated-migrator flow are documented in
Upgrading, with the cursor-rollback procedure in
the recovery runbook. - RC tag gates, all green. The
v1.3.0-rc.1release pipeline completed with
verify-version-consistency, both goreleaser legs (linux + darwin), both package gates (MCP, npm), and
release attestation successful, and both registry publish jobs skipped by prerelease gating. The
tag-triggered Migration Test Harness and Cross-Version Smoke runs both passed. The published
linux_amd64archive checksum-verifies and reports1.3.0-rc.1 (9c6a69ec1). A local validation battery
against the tag passed the full release stability gate — all 7 upgrade scenarios from each of v1.2.2,
v1.1.2, and v1.1.0, including the new wisp-plane leg — plus fresh-workspace, migration-UX (28 steps,
counter restart as documented), forward schema-skew guard, migration-freeze, and journal-actor checks.
Installing
For the v1.3.0-rc.1 prerelease, take the binary from this release's assets — the package managers
below track the latest stable release and will not pick up a prerelease. For the final v1.3.0, use
the command that matches your install method:
# macOS / Linux / FreeBSD
curl -fsSL https://raw.githubusercontent.com/gastownhall/beads/main/scripts/install.sh | bash
# Homebrew (macOS, Linux)
brew upgrade beads
# npm
npm update -g @beads/bd
# go install — server-mode only
CGO_ENABLED=0 go install github.com/steveyegge/beads/cmd/bd@latest
# go install — embedded-capable
CGO_ENABLED=1 GOFLAGS=-tags=gms_pure_go go install github.com/steveyegge/beads/cmd/bd@latest# Windows
irm https://raw.githubusercontent.com/gastownhall/beads/main/install.ps1 | iexIf you still have the old tap formula installed as bd, switch to the Homebrew core formula:
brew uninstall bd
brew untap gastownhall/beads 2>/dev/null || true
brew untap steveyegge/beads 2>/dev/null || true
brew install beadsWhatever you use, run which -a bd afterwards: a second binary earlier in PATH is the fleet-skew
failure described in the upgrading notes.
Contributors
Thanks to everyone who contributed to v1.3.0 (between v1.2.2 and release/1.3.0 — which, because
v1.2.2 re-shipped the v1.1.2 tree, spans the whole 1.2 line's work):
@A3Ackerman, @AJBcoding, @anisoptera, @aphexcx, @arcaven, @aryrabelo,
@athosmartins, @banozz0, @bee-ghosttrack, @boardthatpowder, @brendan-appstart,
@chrisjunlee, @coffeegoddd, @cosentinode, @csauer02-personal-user, @csells,
@cuongbphv, @daniel-jasinski, @davevan2, @DyrtyJax, @ecuthiell, @enieuwy,
@eric-richardson1, @Ethee, @GraemeF, @HackAttack, @harry-miller-trimble,
@heymatthew, @iamthebot, @idirectships, @idvorkin-ai-tools, @imkp1,
@itsandyking, @jacobhausler, @jamelt, @jdelic, @jjgarzella, @johnzook,
@joshuaguyervs, @julianknutsen, @kevglynn, @Kevinwochan, @liviux,
@lumaks-redox, @maphew, @marcodelpin, @marlon-costa-dc, @maxinflection,
@mccraigmccraig, @mlushpenko, @mohamedramadan14, @Mosnar, @MovGP0, @mwotton,
@nova-submodules, @ousamabenyounes, @Photobombastic, @postoso, @prmichaelsen,
@pvinis, @quad341, @RaviTharuma, @remuscazacu, @rjc123, @Rome-1, @ryanwclark1,
@scotthamilton77, @shaunc, @shiminshen, @ShiroKSH, @shon-yuan, @sjarmak,
@srobroek, @steveyegge, @swedeinasia-flow, @thewoolleyman, @Toady00,
@uschtwill, @vishnujayvel, @Wldc4rd, @zach-source, @Zireael
Special thanks to the external contributors whose fixes were carried onto the release branch:
- @marcodelpin (Marco Del Pin), whose diagnosis and fix for dolt#11131 encoding drift lets the migration
path survive tables it cannot read — carried and reworked as #5064 by @maphew (matt wilkie) - @Toady00 (Brandon Dennis), whose #5783 makes server mode honor
Config.LenientOpenso the dirty-table
refusal's own recovery works - @nova-submodules (Nova Latent), whose #5740 diagnosis and fix for server-mode lost updates was carried
as #6040 - @anisoptera (Isis Anisoptera), whose #5625 recognizer for Homebrew
--HEADversion stamps was carried
as #6079, keeping a HEAD build out of the.dolt-deleting pre-v56 recovery
Recommended Reading
- Upgrading guide
- Recovery runbook: cursor rollback
- Dolt architecture: embedded and server modes
- Federation and multi-replica leases
- Events journal reference
- JSON Schema for
--jsonand export output - Observability and OpenTelemetry
bd serve: the v0 wire contract is the checked-in OpenAPI document,
internal/httpapi/spec/openapi.v0.yaml,
with operator guidance in the
serve runbook and
the v0 design note.
The published docs site does not yet carry an HTTP API reference page.- Full changelog, including the withheld [1.2.1] section
Full Changelog: v1.2.2...release/1.3.0