| Name | Modified | Size | Downloads / Week |
|---|---|---|---|
| Parent folder | |||
| beads-v1.3.0.spdx.json | 2026-09-15 | 2.2 MB | |
| checksums.txt | 2026-09-15 | 881 Bytes | |
| beads_1.3.0_darwin_amd64.tar.gz | 2026-09-15 | 53.4 MB | |
| beads_1.3.0_darwin_arm64.tar.gz | 2026-09-15 | 48.3 MB | |
| beads_1.3.0_contract_corpus.tar.gz | 2026-09-15 | 4.1 kB | |
| beads_1.3.0_windows_amd64.zip | 2026-09-15 | 54.0 MB | |
| beads_1.3.0_android_arm64.tar.gz | 2026-09-15 | 31.3 MB | |
| beads_1.3.0_linux_arm64.tar.gz | 2026-09-15 | 49.3 MB | |
| beads_1.3.0_linux_amd64.tar.gz | 2026-09-15 | 53.2 MB | |
| beads_1.3.0_freebsd_amd64.tar.gz | 2026-09-15 | 31.9 MB | |
| beads_1.3.0_windows_arm64.zip | 2026-09-15 | 30.3 MB | |
| README.md | 2026-09-15 | 113.8 kB | |
| v1.3.0 source code.tar.gz | 2026-09-15 | 9.8 MB | |
| v1.3.0 source code.zip | 2026-09-15 | 12.0 MB | |
| Totals: 14 Items | 375.8 MB | 0 | |
Beads v1.3.0
v1.3.0 — the first tested release off main since the 1.1 line. Released 2026-09-15, cut from
release/1.3.0 at f45b249ce.
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-readablecodevocabulary 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 requiresAuthorization: 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 bead
in_progressforever with no recovery verb. Claims now carry a lease —lease_expires_at(default TTL 5m) andheartbeat_at, schema v54 — withbd 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_lockcell, 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, andclaim.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, recomputeis_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 leavingdolt_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 leavesbd 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 withbd 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 typedevents_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-depsprojects 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-FREEZEsentinel 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 so
bd 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 bybd 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 shipped
bdbinary, alongside dependency bumps clearing all 25 open advisories with per-cluster reachability verdicts (#6047). Carried from the same arc:bd dolt pushandbd 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:
```bash # 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=1 is 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
with BD_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 --force from exactly one machine, then bd 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 restart
A 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 binary
bd 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.
bash
# 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 — bare bd backup
takes no backup, it is a command group that prints help and exits 0:
bash
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 second
bdearlier 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 use
bd defer --untilat all, beads you believed would come back have probably not been coming back, and they reappear after upgrading. Andbd purge/bd prunenow 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 publicbackendpackage.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/-yconsents 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 wherebd 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), becausebd 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: plainbd 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 forcingPinned=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 is
VARCHAR(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 addused 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 explicitbd 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 namesbackend.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 nowbd: sweep <n> <tier> bead(s)on both routes, replacingbd: prune <n> bead(s)andbd: 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, sogastown--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.PATCHpublishes 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, migration0054_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-servermany 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 existingUpdateIssueChecked— 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 noclaim.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(github.com). - 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 --briefprintsDescription: (omitted by --brief).--max-rowson the walking reads, plusBEADS_MAX_ROWS. A hard upper bound on rows returned bybd 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 fromsource: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(github.com).- 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 hiddenbd 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 (kubectl
ExecCredential, 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 ofBD_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 toSHOW 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 removesDOLT_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 honorsNO_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 (alsoBEADS_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 anactorcolumn (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 ofLIMIT, 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 listkeeps 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 is409 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, andbd 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:sweepdefaultsprotect_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 behindGET /v0/beads/contextandbd contexthardcodedbackend: doltand copieddolt_modeanddatabaseunconditionally, so a registered-backend workspace was confidently reported asbackend="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 throughbd 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-allandbd 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 statsclassifies dismissals by theDismissedclose-reason prefix shared as one constant withbd 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
shared
dolt 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 --fixpreviously 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 andbd 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 andbd 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.jsonstill 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_rootbecome 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 tostatus=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 actorbd-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 — whilebd 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 of
bd 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_totalandbd.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
fires
on_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-serverroute. (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 printsAdopted 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-historyran a plain column update on whichever table the row already sat in, so the bead stayed inissuescarryingephemeral = 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 proxiedbd 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.customandtypes.customnow project into their tables so the setting and its effect land together (previouslybd 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 proxiedbd 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 offilepath.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 forbd --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- bulletsitting 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, breaking
GOOS=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 everybd 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 newstorage.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 whole
ignoredchain 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 remotebd 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; everyUSEfailure 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 in
expected_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>frombrew 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, andbd 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 theephemeralcolumn 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 inaux_row_rekey_drifted, and re-keyed on a later pass; healing the drift itself is Dolt'sschema-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
_typediscriminator — 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). - An externally-managed Dolt sql-server was classified as bd's own, silently disarming the
shared-store migrate gate (#6118). This is the one to read if you run a shared server. When the
port arrived from
BEADS_DOLT_SERVER_PORT, frombd init --server-port— the documented way to attach a workspace to an existing server — or from a stale port file left by a bd server that had since died and been replaced on that port, bd treated a genuinely shared server as workspace-owned. The gate added by [#5920]/#6048 to prevent exactly this never fired, so bd promoted the shared schema in place with no prompt, no warning and exit 0, after which every older client of that server was hard-refused withschema version mismatch. Ownership is now proven rather than inferred: bd must have bound that port itself, per its own port file, and the PID it recorded must still be alive. Everything else is shared. The pre-existing explicit-external arms run after the proof, so proof of a bd-managed server can never un-gate a topology [#5920]/#6048 already gated. - A
dolt.mode: serverworkspace declared in.beads/config.yamlwas refused as a legacy workspace (#6119). The upgrade guard resolved the connection mode frommetadata.jsonand nothing else, whileConfig.IsDoltServerModedocuments six further layers — one of which isdolt.modein the workspace's own config.yaml. A workspace of that shape resolved to embedded, found a.beads/doltdirectory, and was refused before any store was constructed, so every command failed. The guard now resolves the mode through the same precedence chain store selection uses. - Host-implied server mode was not applied when config.yaml declared a mode (#6120), so a workspace could resolve to embedded against a host that implies a server.
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 fromgit 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 unauthenticatedGET /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 thex/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.
- Two release candidates preceded this tag, and rc.2's content is what shipped.
v1.3.0-rc.1(9c6a69ec1) andv1.3.0-rc.2(c185735c3) each completed the full release pipeline with both registry publish jobs correctly skipped by prerelease gating. rc.2 fixed four defects that only appeared when rc.1 was driven against a real downstream integration: [#6118], [#6119], [#6120] (all described under Fixed) and a CI-only timeout. Thev1.3.0tree is rc.2's tree plus the version stamp — the commit reverts an unreferenced package that no RC had validated, leaving the shipped code byte-identical to what rc.2 was tested as. - The v1.3.0 release pipeline completed green.
verify-version-consistency, both goreleaser legs (linux + macOS), both package gates (MCP, npm), release attestation, and — for the first time, since prerelease gating no longer applies —publish-pypiandpublish-npm. The tag-triggered Migration Test Harness and Cross-Version Smoke Tests both passed. 11 assets published: linux amd64/arm64, darwin amd64/arm64, windows amd64/arm64, freebsd amd64, android arm64, the contract corpus, an SPDX SBOM, and checksums. - Independently qualified against a real downstream consumer before the tag. Gas City ran its full
acceptance matrix against a bd built from the exact release content, with schema parity asserted in
TestMainand noreplacedirective: the seven-shape init-topology matrix (proxied-local, direct-local, direct-external ×2, proxied-external, legacy externally-managed, deferred-init) at 8/8 subtests each, both migration paths including the live-server refusal, and its dashboard probe and pin suites. All pass, zero skips, zero non-zero exits. - 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.
Known issues
- A store whose
eventstable is missing stays write-dead, and 1.3.0 does not repair it (#6142). Reads all succeed, so the store looks healthy until the first write, which fails with a rawError 1146: table not found: events. The cause is thatevents,bd_events_journalandbd_events_seqare created by the clone-local migration series but are not among its sentinel tables, so a cursor that claims they were created is believed even when they are absent and the creating migration is never replayed. 1.3.0's new cursor-reality floor healswispsandwisp_dependenciesthis way but not these three. #6547 adds them to the floor and is held for 1.3.1 rather than landing after the release candidates had already been qualified. - Proxied-server mode has no backup path.
bd backupandbd restoreare refused in proxied-server mode. Use server or embedded mode if you needbd backup, or take backups through Dolt remotes withbd dolt push. bd dolt startis not proxied-aware. On a proxied-server workspacebd dolt statusreports the server as not running, andbd dolt startwould launch a second, unmanageddolt sql-serverover the same data directory. Use the proxy's own lifecycle rather thanbd dolt starton those workspaces.
Performance
1.3.0's proxied-server topology changes the resource profile rather than raw throughput, and the
difference is in what a workspace costs when nobody is using it. Measured on one host with bd built
from the release content, comparing a per-workspace dolt sql-server against proxied-server mode with
its built-in 30-second idle timeout:
| Workspaces | Direct — idle | Proxied-local — idle | Proxied, one shared backend — idle |
|---|---|---|---|
| 1 | 130 MB, 1 process | 0 MB, 0 processes | 123 MB, 1 process |
| 5 | 616 MB, 5 processes | 0 MB, 0 processes | 185 MB, 1 process |
| 20 | 2456 MB, 20 processes | 0 MB, 0 processes | 603 MB, 1 process |
Direct mode's idle footprint scales linearly with workspace count and is held indefinitely; a proxied workspace reaps its backend once idle and returns to zero. At 20 workspaces that is 2.46 GB reclaimed.
The honest counterweight: proxied mode costs more while active — 3445 MB against direct's 2483 MB at 20 workspaces, because a proxy process sits alongside each backend — and a reaped workspace pays a cold start on next use, measured at 0.30–0.63 s across every arm and scale. Idle CPU is not the interesting axis: direct burned roughly 1.6 CPU-seconds per server per hour, real but small, and proxied burned none because the processes no longer exist. If your workspaces are continuously busy, direct is cheaper; if you carry many workspaces that are idle most of the time, proxied is dramatically cheaper.
Caveats worth stating: single host, a dolt CLI one patch behind the pinned version, and a machine
under heavy unrelated load — so treat the figures as representative of the shape, not as precise
benchmarks.
Installing
Use the command that matches your install method. The package managers below track the latest stable release, which v1.3.0 is; the binaries attached to this release are there if you would rather take one directly:
:::bash
# 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
:::pwsh
# Windows
irm https://raw.githubusercontent.com/gastownhall/beads/main/install.ps1 | iex
If you still have the old tap formula installed as bd, switch to the Homebrew core formula:
:::bash
brew uninstall bd
brew untap gastownhall/beads 2>/dev/null || true
brew untap steveyegge/beads 2>/dev/null || true
brew install beads
Whatever 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(github.com), 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: https://github.com/gastownhall/beads/compare/v1.2.2...release/1.3.0