Skip to content

v1.3.0-rc.1

Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 31 Aug 08:06
· 11 commits to main since this release
9c6a69e

Beads v1.3.0

v1.3.0-rc.1 — release candidate for v1.3.0. Changelog section dated 2026-08-28; cut from
release/1.3.0 at b3ef65c8.

Beads 1.3.0 is the first tested release off main since the 1.1 line, and it carries everything
that has landed since — 1,342 commits arriving in a single upgrade.

That is not the usual increment, and the reason is worth stating plainly. The v1.2.0 tag burned
pre-publish on a FreeBSD cross-compile failure and was never reused, so there is no [1.2.0]
changelog section — the [1.2.1] section is the 1.2.0 content. v1.2.1 shipped on 2026-08-11, but
published by accident, without release testing, and it auto-migrated local databases to a schema
(v54–v65) no supported binary knew. Four days later
v1.2.2 recovered the line by re-releasing
the tested v1.1.2 tree under a higher version number, off branch release/v1.2.2 rather than
main. go.mod retracts v1.2.1, v1.2.0 and v1.1.1. So the version numbers moved forward while the
code did not, and 1.3.0 is where main's work reaches users for the first time.

What that means for you, concretely:

  • On v1.2.2 — you are running v1.1.2-era code. You receive the entire [1.2.1] section (2,236
    changelog lines, the largest in the file) and everything in [1.3.0]. Whole subsystems below —
    an HTTP API server, work leases, a durable events journal, bd sync, a public Go API — do not
    exist in your build in any form. This is the upgrade to read the notes for.
  • On v1.1.2 — identical to the above. v1.2.2 is your tree with a different version string; the
    only functional difference is a forward-skew error message.
  • On v1.1.0 — the same, plus the [1.1.2] dolt#11131 aux-row-rekey drift fix (#4380), which is
    a data-availability fix and is re-listed under Fixed below.
  • On v1.2.1 — you should not be; it is retracted. bd will stop with schema 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 serve binds 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 9457 problem+json with a frozen machine-readable code
    vocabulary and one HTTP status per code, so a client classifies a claim conflict from a typed 409
    instead of substring-matching error prose; listing pages with an opaque keyset cursor that does not
    expire and survives restart; GET /v0/beads/context reports which operations the running build
    actually implements. The spec is hand-written and checked in, the Go wire types are generated from
    it, and make api-check fails a change that edits one without the other. This is 100% net-new on
    upgrade
    internal/httpapi/spec/openapi.v0.yaml does not exist at v1.1.2 — and it is the
    subsystem the current release notes had been missing entirely.
  • bd serve is deployable, not just a loopback toy (#5516). --auth-token-file names a file of
    accepted bearer tokens, one per line; every operation except GET /healthz then requires
    Authorization: Bearer <token>, including GET /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-token flag: a
    credential in argv is readable out of ps. --allow-non-loopback now requires a token file, with
    --insecure-no-auth as the explicit auditable opt-out, and --allowed-host extends 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, actor stays 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_progress forever with no recovery verb. Claims now
    carry a lease — lease_expires_at (default TTL 5m) and heartbeat_at, schema v54 — with
    bd heartbeat <id> to extend it, bd reclaim --older-than <dur> to revert expired ones back to
    ready, and bd unclaim to give one back (#4537). Because Dolt has no row locking and merges
    concurrent commits cell-by-cell, every ownership-mutating path also rewrites a shared row_lock
    cell, forcing a racing heartbeat-vs-reclaim into a serialization conflict the retry layer replays
    rather than cell-merging into a zombie claim; the same landing wrapped the work-queue hot paths in
    serialization-conflict retry, so N workers draining one queue stop surfacing raw MySQL 1213/1205
    errors. Leases are replica-aware: leases.granted_node records the granting replica and bd reclaim skips a lease another replica granted unless you pass --any-replica (wy-jpd3.7). The
    guard is opt-in and fail-open, armed by node_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-status apply 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": true per failed entry under --json. That is
    what makes bd 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-assignee is the inverse spelling, and
    claim.pools (bd-bguz6) turns the dispatcher-fleet pattern into a one-step atomic take: aliases
    listed in bd 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 sync is the federation loop as one verb (wy-jpd3.4). Pull, detect conflicts, recompute
    is_blocked, push, with bounded retry (default 3) when another replica wins the push race. Two
    properties are the point. Conflicts are detected positively, from the merge's own captured
    conflict rows and from dolt_conflicts, never inferred from the pull's exit status — a pull fails
    for plenty of reasons that are not conflicts, and a settled-but-conflicted merge aborts leaving
    dolt_conflicts empty, 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 --strategy override. The recompute between pull and push is not bookkeeping —
    is_blocked is denormalized, so a merge that brings in another replica's dependency edge leaves
    bd ready stale 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|resolve is the companion that clears an exit-2 halt without
    dropping into the raw dolt CLI.
  • A durable events journal, and bd events to 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 including is_blocked — so a downstream
    mirror stays correct without re-querying the graph. Off by default; opt in per workspace with
    bd config set events-journal true or BD_EVENTS_JOURNAL=1. bd events tail --since <seq> prints
    JSON lines and --follow streams as writes commit; bd events export prints from the beginning;
    bd events prune --before <seq> takes an earlier cut. A read that cannot resume fails rather
    than lying
    : a --since below the oldest retained record exits 1 with a typed
    events_journal_truncated error carrying since, floor and head. Retention floors
    (events-journal-retain-days 7, events-journal-retain-rows 100000) 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: --brief and --brief-deps (#5546,
    #5547, #5549, #5554, #5586). On one real 16,051-bead store bd ready --json returned 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. --brief on bd list and bd ready omits the free-form text
    (description, design, acceptance_criteria, notes, payload, waiters) and measured
    93.4% smaller. Separately, one bd show --json returned 214,456 bytes of which 193,039 was
    the dependencies array, one dependency carrying a 180,975-byte notes field; --brief-deps
    projects each dependency to id, title, status, issue_type, priority, dependency_type and
    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 schema after the fleet is upgraded, or BD_ALLOW_REMOTE_MIGRATE=1 in scripted use (#5920, #6048).
    The gate also runs on the proxied bd serve open 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 a MIGRATION-FREEZE
    sentinel with a dedicated exit code 14 (#6043), and bd doctor honors --readonly and 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 pull unwedges (#6046), bd delete no 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-mode DOLT_ADD/DOLT_COMMIT now 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: --until set status=deferred and a timestamp, but nothing ever flipped the status
    back, so an expired defer stayed invisible to bd ready forever — 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-any was emitted by
    bd list but silently dropped by bd ready and bd ready --claim on 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-metadata scalar
    typing, and bd create --json nano timestamps (#6053); --storage-class wired through the proxied,
    markdown, and graph create paths (#6060); journal delete and cascade rows carry the request actor
    (#6061); and GET /v0/beads/issues gains sort with order-bound cursors (#5666).
  • Errors tell the truth. bd bootstrap probes git remotes for Dolt data instead of rejecting
    them, and exits non-zero when it declines (#6037); proxied/gateway bd init attributes identity
    safely and classifies access denials honestly (#6062); the legacy-backend tombstone names the exact
    heal instead of destructive advice (#6044); a Homebrew --HEAD version stamp is recognized rather
    than misread as 0.0.0 and routed into a .dolt-deleting recovery (#6079); bd prime says 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 bd binary, alongside dependency bumps clearing all 25 open advisories
    with per-cluster reachability verdicts (#6047). Carried from the same arc: bd dolt push and
    bd sync no longer adopt a git-origin-derived Dolt remote without consent (#5068), the settings
    plane no longer serves the kv. 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_columns through 0066_add_events_journal_actor. Two of those passes rewrite rows
    rather than just reshaping tables — the aux-row id rekey and the events dolt_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-server is never auto-migrated (#5920, #6048).
    A server-mode database is served to every client attached to it, so migrating it promotes the schema
    for all of them at once and locks out anything still on an older binary. Upgrade that server's
    clients first, then consent once:

    # 1. upgrade bd on every client of the server; reads keep working throughout
    bd version                     # on each client, confirm the new version
    
    # 2. once, from a workspace already set up against this server:
    bd migrate schema              # add --global for the shared global database
    
    # 3. confirm
    bd doctor

    Between steps 1 and 2 an upgraded client reads normally and its writes are refused with the gate's
    guidance — nothing is silently promoted, so there is no deadline, but keep the window short.
    BD_ALLOW_REMOTE_MIGRATE=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 export triggers 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 refuses bd dolt push and bd dolt pull as well, not just bd migrate.

    # with your CURRENT bd, before installing 1.3.0:
    bd dolt push                                                   # remote-backed stores only
    bd export --all -o .beads/backup/pre-1.3.0-$(date +%Y%m%d).jsonl

    A JSONL export is cheap, issue-complete, and importable by any bd version. If you want a Dolt-native
    snapshot that keeps history and config, configure a destination and sync it — bare bd backup
    takes no backup
    , it is a command group that prints help and exits 0:

    bd backup init <path-or-dolthub-url>   # once, to configure a destination
    bd backup sync                         # take the snapshot
  • Upgrade every client that shares a store, together. The forward schema-skew guard means an older
    co-resident binary — a second bd earlier in PATH, a long-running bd 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. Run which -a bd after
    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 --until at all, beads you believed would come back
    have probably not been coming back, and they reappear after upgrading. And bd purge / bd prune
    now select candidates by tier (#5995), so wisps minted before the ephemeral column 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 public backend
    package. sqlite, postgres and mysql survive only as recognized-and-rejected names so a stale
    "backend" field in .beads/metadata.json produces a targeted heal instead of a confusing failure
    (#6044), and bd migrate legacy-sqlite --source-db PATH is 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 review prints exactly the entries between the version you were
    running and this one.
    Prefer it to bd 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, matching bd close. Override with bd update --force; an unforced refusal
    rolls back the entire batch.
  • bd search includes closed issues by default (bd-t5yex). Narrow with --status open for the
    old behavior; bd list keeps 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 close is interactive-only (#4839).
    A scripted bd update $ID … with an empty $ID now refuses instead of mutating whatever was
    touched last. Set BD_LAST_TOUCHED_FALLBACK=1 if a script genuinely relied on it.
  • bd human list hides done/frozen and pinned beads by default, and validates --status (#5332).
    A --status typo is now an error rather than an empty list.
  • bd dolt push and bd sync no longer adopt a git-origin-derived Dolt remote without consent
    (#5068). Adoption now prompts (defaulting to no) and fails closed non-interactively; --yes/-y
    consents ahead of time, and --no-adopt / BD_NO_REMOTE_ADOPT=1 disables it entirely and wins over
    --yes. CI and cron that relied on the silent adoption will now fail closed.
  • bd --readonly serve is 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-running bd serve as part of the fleet upgrade above.
  • bd config list and GET /v0/beads/config no longer enumerate the kv. plane, which is where
    bd remember memories live — that closed an unauthenticated endpoint handing out every stored
    memory. A later landing in the same era closed point reads too: bd config get kv.memory.<slug> now
    answers exactly as a key nothing ever stored does (echoed key, empty value, nil error), because
    bd remember derives its key from the content it stores and the names are guessable. Use bd kv,
    bd recall and bd memories, which read the store directly and are unaffected; bd config set/unset still take a verbatim kv. key as the escape hatch for a wedged memory.
  • bd delete --force on a team server orphans dependents instead of deleting them (bd-x82so).
    Against a proxied server bd delete X --force used to delete X and its whole dependent subtree,
    and --cascade was refused outright. Both routes now carry the local database's meanings: plain
    bd delete X is refused if anything outside the request depends on X, --force orphans dependents,
    --cascade --force takes the subtree. A script that relied on the implicit cascade will start
    leaving orphans — add --cascade.
  • bd dep cycles --json emits 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 --ready refuses a filter it cannot honor (bd-yby99.8) instead of silently ignoring it.
    bd list --ready --id tst-d68 used to answer with every ready bead in the workspace. Refused with
    --ready: --id, --title, --spec, the *-contains family, --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=all stops hiding pinned beads (#5332). all promises every status, but the
    pinned exclusion compared the raw selector string, so all fell through and kept forcing
    Pinned=false — and --status=pinned,closed excluded the pinned beads it explicitly asked for. Add
    --no-pinned to 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 read max 50 chars now name 32.
  • Text-input commands refuse two sources (#5332). bd comment, bd note and bd comments add
    used to let --stdin/--file win 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/remove over many beads is no longer one atomic transaction (ga-26w10, #5489).
    bd label add a b c mylabel records 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/off actually defer version commits in SQL-server mode (bd-4wamg).
    The mode was silently inert there. If you explicitly configured dolt.auto-commit: off, it was
    behaving like on; it now genuinely stops minting version commits until an explicit
    bd dolt commit. Proxied-server routes never apply auto-commit policy, so batch/off remain inert
    there.
  • The published backend package drops orphan handling (bd-gwryr). A Go consumer that names
    backend.OrphanHandling or its constants no longer compiles. Delete the option — every call site
    passed OrphanAllow, which is exactly what the code still does.
  • bd purge / bd prune version-control messages changed (bd-pn231). The entry is now
    bd: sweep <n> <tier> bead(s) on both routes, replacing bd: prune <n> bead(s) and
    bd: delete <n> issue(s).

New in 1.3.0 (full entries under Changed):

  • --profile is 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_PORT environment
    variable.
  • Actor matching decodes an exact -- run to / instead of collapsing it to a generic separator, so
    gastown--mayor matches gastown/mayor and stops matching gastown__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 a bd subprocess forked per call. Reads: context,
    ready, issues
    (list, get, query, count, related, comments), stats, dependencies (list, count, tree, blocking,
    cycles), config and config/{key}, memories, events and events:watch. Writes: :claim,
    :claimNext, :release, :close, :reopen, PATCH /issues/{id}, :batchCreate, :batchClose,
    :batchApply, :delete, :sweep, :casMetadata, dependencies:add/:remove. Close and reopen
    are idempotent and say so in the body (already_closed: true) rather than in a status. PATCH
    publishes nineteen members, four of them nullable where an explicit null clears them — a closed,
    machine-checked set, so a null on any other member is a 400 rather than an unannounced clear.
    expected_version on close/reopen/delete plus revision on 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:0 takes an ephemeral port with no mutual exclusion
    — pass an explicit port. Probe readiness with GET /v0/beads/ready?limit=1; /healthz is 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_FILE is the environment fallback;
    401/unauthenticated joins the frozen code vocabulary with a fixed detail that never echoes the
    presented credential, and missing header, wrong scheme and unknown token are deliberately one code.
  • Bd-Project-Id request stamping, and the project.enforce capability. 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 /healthz and GET /v0/beads/context are 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 carrying server_project_id, so its presence is the signal that
    this check fired rather than the Host gate or a deployment's auth layer.
  • Work leases: bd heartbeat, bd reclaim, bd unclaim (#4537, schema v54, migration
    0054_add_lease_columns — the first of the 13 main-series migrations this upgrade applies). See
    Highlights. bd reclaim also 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_node rides the JSONL
    interchange so an imported lease keeps its provenance, and --any-replica is the escape hatch for a
    replica that is permanently gone. Deliberately no hostname fallback: on a shared dolt sql-server
    many hosts are clients of one store, and a hostname guard there would stop a supervisor reaping any
    worker's lease at all. Documented limitation: a heartbeat proves the holder is alive, not that the
    lease moved, so granted_node is 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), and bd conflicts list|show|resolve
    (wy-jpd3.5) as the companion that clears an exit-2 halt. bd conflicts show renders each conflicted
    row field by field (only disagreeing fields unless --all-fields); bd conflicts resolve takes
    named beads row by row or whole tables with --all, via --ours/--theirs/--strategy, then
    concludes the merge. Note bd conflicts is 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's ExpectedVersion row CAS. Library consumers
    get them through new UpdateIssueOptions.ExpectedAssignee/ExpectedStatus on the existing
    UpdateIssueChecked — no interface change. An out-of-tree Storage implementation that ignores the
    new fields simply does not enforce them, and should add support before advertising guard semantics.
  • Pool-aware claiming via the claim.pools config key (bd-bguz6). Off by default; with no
    claim.pools configured, behavior is unchanged. One gotcha: if a pool take's lease expires,
    bd reclaim returns 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 are GET /v0/beads/events and a streaming GET /v0/beads/events:watch. Not journaled, by
    design: bd dolt pull, merge-settled changes, raw bd sql DML, store-open migrations and compaction
    rewrites. Size the retention floors for the longest outage a consumer must survive — they are a
    recent-window guarantee, not a consumer watermark. Reference:
    docs/reference/events-journal.md.
  • A public Go API: the backend and issueops packages plus a conformance suite (bd-h3dib.2,
    #4415, bd-yby99, #4911, facade waves 1–4). See Highlights. Note the beads.Storage interface 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.
  • --brief on bd list / bd ready and --brief-deps on bd show (#5546, #5547, #5549, #5554,
    #5586), with brief / brief_deps on the HTTP twins. See Highlights for the measurements. Refused
    where it could not be honored: on bd ready it requires --json and is refused with --claim,
    --gated, --mol and --explain; on bd list it works in text mode but is refused with
    --watch, the --parent tree walk, and --format, where a caller's template could print a dropped
    field with nothing to mark it. Text mode carries the single visible marker: bd list --long --brief
    prints Description: (omitted by --brief).
  • --max-rows on the walking reads, plus BEADS_MAX_ROWS. A hard upper bound on rows returned by
    bd list, bd ready, bd graph, bd dep tree and bd 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, --ref with --ref-kind (git-sha, pr, work-id, transcript,
    branch). Append-only — no update, no delete — and idempotent on a deterministic id computed from
    source:issue:kind:(ref or --at), so a git hook or CI job that fires twice is harmless. beads never
    interprets the actor or the ref; only kind and ref-kind are structurally validated. This is the
    supported way to bind beads to commits, PRs and transcripts without abusing labels or metadata.
  • bd schema — a published JSON Schema for --json and export output (#5098). Reflected from the
    same Go structs bd actually serializes, so it cannot drift from the real output; named string enums
    carry their allowed values and referenced types are inlined, so each record schema is self-contained
    and directly consumable by codegen (bd schema | jq '.types.issue'). Runs without a workspace or
    database. Reference:
    docs/reference/json-schema.md.
  • Cursor agent hooks, and Cursor at parity with Claude Code and Codex. bd setup cursor installs
    .cursor/hooks.json alongside the rules file, wiring three lifecycle events to a hidden
    bd cursor-hook: sessionStart injects full bd prime context into every new agent session,
    preCompact arms a one-shot refresh marker, and postToolUse re-injects bd prime exactly once
    after a compaction then no-ops — so beads context survives compaction instead of being forgotten
    mid-session. Existing user hooks are preserved and --remove only removes the beads-managed entries.
    Parity work rides along: bd init auto-installs Cursor the way it already does Claude Code and
    Codex; --global writes ~/.cursor/hooks.json and the agent skill to ~/.agents/skills/beads;
    .cursor/rules/beads.mdc now wraps the shared recipes.Template instead of a hand-maintained copy
    that drifted; and bd doctor reports Cursor Integration, Cursor Settings Health and Cursor 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, AWS credential_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 via BEADS_DOLT_CREDENTIAL_COMMANDenvironment
    only, deliberately not metadata.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 (and bd migrate schema --force) is the CLI twin of
    BD_ALLOW_REMOTE_MIGRATE=1 for 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=0 opts 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 DATABASE only moves a database's directory under .dolt_dropped_databases/; Dolt
    keeps the data there — recoverable via CALL DOLT_UNDROP(name) — until an explicit purge, so disk
    usage on a shared server stayed high across repeated cleanup runs. The flag runs that purge, and
    fires even on a run that finds nothing stale, since a prior run's residue is not visible to
    SHOW DATABASES. Read the warning as written: CALL DOLT_PURGE_DROPPED_DATABASES() is
    server-global and irreversible — Dolt cannot scope it to the databases a given run dropped, so this
    also permanently deletes every other dropped-but-unpurged database on the server and removes
    DOLT_UNDROP recovery for all of them. It defaults to off for exactly that reason.
  • Deployment-mode migration commands, and bd migrate legacy-sqlite. Four bd migrate mode-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 their dolt sql-server at the same .beads/dolt directory,
    so the switch is a config change rather than a data move. This matters because bd serve refuses
    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-personal moves your own planning beads out of a
    project database into your personal planning repo.
  • New global flags: --no-color, --database, --mem-profile. --no-color also honors
    NO_COLOR=1 / CLICOLOR=0. --database runs 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 --db value that is not an existing path is now
    treated as a database-name override. --mem-profile FILE writes a heap profile on exit (also
    BEADS_MEM_PROFILE).
  • New bd init deployment 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 reads export.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); and bd formula schema, which prints every exported struct
    a .formula.toml/.formula.json can declare, generated from source so it cannot drift.

New in [1.3.0]

  • The events journal records WHO performed each mutation. bd_events_journal gains an actor
    column (migration 0066 plus its ignored-series twin 0025), stamped inside the mutating transaction
    with the same identity the audit-events table resolves. bd events tail / bd events export JSON
    gains an additive, omit-when-empty actor field — empty means the path had no actor, never a user.
    Delete and cascade dep_remove rows carry the request actor too, and reading a disabled journal now
    says so instead of returning silence (#6061, #5985).
  • sort on GET /v0/beads/issues (#5666). Two values, and the set is closed: created (the
    existing order, now spellable) and priority (bd list's flagless ordering) — measured over a
    1400-row store at the 200-row page an HTTP client actually uses, ?sort=priority turns a 7-request
    page-and-resort walk into 1 request. Cursors are now v2 tokens that carry their order, and a cursor
    replayed under a different sort is refused as invalid_cursor instead of silently paging a
    different total order; outstanding v1 tokens stay readable, so no traversal in flight has to
    restart. Absent sort still means created, permanently.
  • bd warns 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;
    --quiet silences it. bd doctor gains a matching warn-only Label Whitespace check, and bd doctor --fix repairs damage already in the database.
  • bd stale and bd blocked gain --label, --label-any and --exclude-label (#5822), with the
    same meanings they already have on bd list and bd ready. Filtering happens in SQL ahead of
    LIMIT, so --label x --limit 10 returns ten matching beads.
  • New *.gate.lock files 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 like bd backup restore takes 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 pattern bd doctor maintains.
  • 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 search includes closed issues by default (bd-t5yex). The dominant real-world query is "was
    this already found/filed/fixed?", exactly the query where silently excluding closed beads produced a
    false "no" — downstream repos had grown shell wrappers purely to undo the old default. bd list
    keeps its open-only default, and bd query (plus the HTTP q= 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 covers conditional-blocks, which previously skipped cross-type validation entirely. A
    task gated on an epic becomes ready when the epic closes. Over HTTP the refusal is
    409 dependency_cycle carrying issue_id, blocker_id and blocker_is_ancestor; the absence of
    those members is what tells a client it got a plain scheduling cycle instead.
  • bd delete is 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 and bd serve. Consequences beyond the
    team-server break listed above: bd delete --cascade on a wisp no longer marks live beads as
    deleted in other beads' text (the rewrite set was computed differently from the delete set, and
    bd wisp gc ran exactly this path), a one-id delete without --force refuses 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-file batches. Text
    already corrupted by earlier builds is not repaired.
  • bd purge and bd prune select 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 --pattern or --closed-before. Over HTTP, POST /v0/beads/issues:sweep defaults
    protect_referenced to true where the CLI's --ignore-references is opt-out, on purpose: being
    authenticated says nothing about whether this particular deletion was meant.
  • bd create --file is 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 single bd create goes 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-type and --validate are 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 — check bd list before re-running a plan that errored on an older version. bd create --graph is untouched.
  • bd serve runs on a registered storage backend, and context stops guessing. bd serve alone
    used to reimplement a creation path hard-wired to Dolt's SQL wire; it now uses the store the root
    command already opened, through the same registry dispatch — one creation path rather than two, with
    the startup line naming both source and backend. Relatedly, the shared identity projection behind
    GET /v0/beads/context and bd context hardcoded backend: dolt and copied dolt_mode and
    database unconditionally, so a registered-backend workspace was confidently reported as
    backend="dolt", dolt_mode="embedded", database="beads" on the one endpoint automation is told to
    trust for a server's identity. The backend is now named as the store open resolves it, which also
    closes a latent CGO_ENABLED=0 bug 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_at on 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 moved updated_at past the merge base, because taking one
    side's whole row would drop the other side's field edits. A field-level three-way merge now encodes
    beads' actual write semantics — a column only one side changed relative to the merge base keeps that
    side's value, and only a genuinely contested cell falls to last-write-wins. Reached through
    bd dolt pull and bd sync; there is no flag.
  • bd label add/remove over 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-all and
    bd label propagate are unchanged and deliberately stay off the role.
  • Text-input commands refuse two sources, and bd human matches bd list (#5332). See the breaking
    list for bd comment/bd note/bd comments add and --status=all. Alongside them: bd human respond gains the same input layer (--response is no longer required) and bd human dismiss takes
    its reason positionally; bd human list matches bd list's status handling while hiding no bead
    type, because a human label is an explicit request for a person's attention; and bd human stats
    classifies dismissals by the Dismissed close-reason prefix shared as one constant with
    bd human dismiss, instead of matching the substring dismiss anywhere 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/off actually 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 to off while behaving like on, and now resolves to on, naming what a default
    server-mode write has always done. On a shared server, staging is table-level, so an on-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-server accepts 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 proxied bd serve open 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 serve fails to start until the schema is reconciled, bd migrate schema works 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 doctor respects --readonly and the migration freeze (#6056; fixes #6028). bd doctor --fix
    previously mutated workspaces that strict --readonly and an active freeze had both declared
    off-limits — and plain bd doctor, with no --fix at all, could rewrite .local_version or
    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-FREEZE marker is present (dc-6jaq, #6043).
    Discovery is generic: the marker is found in the workspace directory, the working directory, or any
    ancestor of either, and BD_MIGRATION_FREEZE_FILE is authoritative when set. Every write command
    (~120 call sites, bd q included), plus bd init --reinit-local and bd bootstrap, prints
    ⛔ workspace is frozen for migration naming 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 running bd serve is 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 events audit table is now clone-local (#5162). Migration 0062 moves events onto the
    dolt_ignored plane, preserving every row. Events keep full SQL durability locally and
    bd history <id> --events still reads them, but events rows 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.jsonl audit sidecar is opt-in (#4688). audit.enabled defaults to false and
    bd init no longer creates the file; the database-backed replacement is bd 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.enabled always wins, and bd config get backup.enabled now prints the effective value with
    its source.
  • --profile is now --cpu-profile, with no alias (#5126). The old spelling fails as an unknown
    flag instead of silently doing nothing.
  • bd import refuses a redirected stdin rather than quietly ignoring it (#5171).
    bd import < file.jsonl looked like bd import - and behaved like bare bd import; it now errors
    and names both fixes.
  • bd hooks install --chain and --force are 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_PORT environment 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 legacy BEADS_DOLT_PORT spelling no longer overrides
    configured ports. Ports bd resolved for itself stay overridable by either spelling, and a stale
    .beads/dolt-server.port left by a crashed server no longer makes auto-start refuse over a port
    nobody configured.
  • The legacy-backend tombstone rejection is truthful (#6044). A workspace whose metadata.json
    still says "backend": "sqlite" (v1.2.x opened these as Dolt anyway) is still refused fail-closed,
    but when live Dolt data is detected the message now carries the exact one-field heal instead of
    advising an export/reinit that would have destroyed a database one edit from healthy. Detection helper
    ported from @steveyegge's #4740.
  • The new exported Go packages are marked experimental before the tag freezes them (#6036).
    backend, beadserrors, issueops, journalops, memoryops, schema and 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 show labels created_by as Created by:, not Owner: (be-ss66); bd dep add names the
    implicit type=blocks default to interactive operators (#5854); bd reclaim summarizes replica-guard
    skips in one line instead of one line per stranded lease (wy-sp2l4); beads_dir and repo_root
    become OPTIONAL in ContextResponse (bd serve still 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 --until and bd update --defer set status=deferred plus a defer_until timestamp, but
    nothing ever flipped the status back — so an expired defer stayed invisible to bd ready forever
    until a human ran bd 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 with status=deferred and defer_until <= now flips to
    status=open, defer_until=NULL, byte-identical to what bd undefer writes so a later dateless
    re-defer cannot inherit a stale past date. Each wake records a status_changed event with actor
    bd-defer-wake. What does not change: a dateless defer is the indefinite icebox and never
    auto-wakes. The sweep is a no-op when nothing has expired (no Dolt commit is minted), advisory by
    contract (a ready listing never fails because the sweep could not run; strict --readonly skips it),
    and identical in embedded, server and proxied-server modes.
  • --label-any is no longer silently dropped by bd ready and bd ready --claim (bd-s10oa). The
    ready-work WHERE builder emitted clauses for --label and --exclude-label but none for
    --label-any, so the OR-set filter was ignored on the ready and claim paths on every backend — while
    bd list and bd search honored 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 --claim now honours the directory-label scope, where the branch applying a directory's
    configured directory.labels default tested the filter it had already written the default into, so it
    could never fire.
  • bd query no longer drops matches from an OR or NOT query (bd-pohmh). To answer an expression
    the storage filter cannot express, both routes fetched max(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 --limit matches and the truncation hint is exact.
    --offset N now works with OR/NOT where it used to be refused outright. Your results will
    change, and they change by getting bigger.
    The cost is real: a broad OR/NOT expression 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 metrics bd.claim_verify_lost_total and
    bd.claim_verify_recovered_total. Applies to claim, ready-claim and unclaim in server mode; wisps and
    embedded mode are unchanged.
  • Script hooks now fire on both write plumbings, and a command waits for its own hooks (bd-opisf).
    bd has two write plumbings and only one ran the workspace's hook scripts. The storage decorator chain
    fires on_create/on_update/on_close after 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 from main before a hook had even started; the wait
    happens after the store closes, so a hook script's own bd can open the workspace. bd serve still
    runs no hooks, and bd import still fires none on either plumbing.
  • Team-server data corruption: proxied bd init, bd create --id, and bd update --ephemeral
    (bd-zl3u8, bd-7oyh5, bd-xt6de). Three independent silent-corruption bugs on the --proxied-server
    route. (1) bd init --proxied-server against a team server whose database another rig had already
    identified wrote its own locally-derived issue prefix and project id straight over the stored pair —
    renaming every id the co-tenant was about to mint. It now reads the identity first and prints
    Adopted project identity from existing database; there is no flag that restores the overwrite.
    (2) bd create --id <id> on a proxied server silently upserted every column of a resident bead —
    title, type, description, labels — and exited zero, with no history entry naming a create and nothing
    in the output to suggest anything had been replaced; both routes now return an already-exists error
    naming the id and leave the stored row untouched. (3) Proxied bd update --ephemeral/--no-history
    ran a plain column update on whichever table the row already sat in, so the bead stayed in issues
    carrying ephemeral = 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 --ephemeral fix does not repair existing rows. Any bead a proxied
    bd update --ephemeral produced before this release is still in issues with the flag set, and
    nothing here finds it.
  • More team-server parity, from the facade programme. Proxied bd config set status.custom and
    types.custom now project into their tables so the setting and its effect land together (previously
    bd config set types.custom "session" reported success and bd create -t session failed for as long
    as the workspace lived); bd config set-many issue_prefix=<x> is refused as bd config set always
    was, where it used to walk past the guard and re-prefix the workspace; proxied bd reopen reopens a
    bead in any configured done status; bd update --parent on a proxied server replaces every parent
    edge instead of stopping at the first; bd dep remove on a wisp-sourced edge actually removes it; a
    dependency on a bead in another repository no longer fails with a raw foreign-key Error 1452;
    bd dep tree on a team server resolves partial ids and honors --max-rows; and proxied
    bd create/reopen/update --json emit the direct route's shape, gaining the labels field they
    were silently dropping.
  • bd purge --pattern / bd prune --pattern stop reporting success on a malformed glob, and
    --dry-run matches the real run
    (bd-pn231). Both routes matched with the error return of
    filepath.Match discarded, so bd prune --pattern '[' --force printed No closed beads to prune and
    exited 0 — indistinguishable from a correct pattern over an empty set, which is the worst possible
    failure mode for a cleanup script. And --dry-run asked for the refuse-if-any-external-dependent path
    while the confirmed run asked for the force path, so a dry run could report has dependents not in deletion set for a sweep --force finishes, with the CLI then swallowing the error and printing
    zeros.
  • bd dep cycles is 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 the Found N dependency cycles count entirely. Diffing two cycle snapshots is now
    meaningful, which it never was. The same sweep backs the post-add cycle warning that bd dep add,
    bd dep --blocks and bd link print, on both routes. See the breaking list for the --json shape
    change.
  • Telemetry no longer taxes every bd invocation. 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 (ioreg on macOS, measured at 20.2±1.2ms) — even with BD_DISABLE_METRICS=1 and even for
    bd --version; it is now resolved only when metrics are enabled and cached at ~/.beads/machine-id
    (0600), with a probe failure retried next run rather than cached. Second, every invocation
    unconditionally spawned a detached bd send-metrics child — 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. Every bd call 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 beside Owner: and Created:, so it never wrapped
    and its second and later lines read as separate metadata entries — a blank line and a bare - bullet
    sitting directly under Created:. bd close --reason-file exists 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 a CLOSE REASON section rendered by the same call the other body fields make; the JSON
    payload is untouched.
  • Smaller listing and reporting corrections. bd list and bd children print 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-rows reports its own
    malformed value before the filter's. bd status --assigned reports the underlying error instead of a
    causeless "failed to get assigned statistics". bd count is one implementation again on a shared role,
    with byte-identical output. Multi-id bd dep list no 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
    suggesting bd 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-bead bd create --id P.N no longer
    leaves child_counters stale, 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=freebsd compilation —
    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_migrations is untracked at open (#6046; fixes #4356). On legacy
    lineages the clone-local migration cursor predated its dolt_ignore pattern and sat committed at
    HEAD, so every migration pass dirtied a tracked table bd could never clean — push worked and every
    bd dolt pull died with cannot 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 delete no longer wedges auto-export forever (#6059, completing #5806; fixes #5896).
    Auto-export's orphan guard refused to overwrite an issues.jsonl holding a deleted bead's record,
    refused again on every subsequent command, and the documented recovery re-imported the JSONL —
    resurrecting the bead the user deleted. Deletions are now proven via store history (a new
    storage.HistoryPresence capability 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 ignored chain
    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 no leases table, so the sentinel was always missing. That replay dragged in ignored/0007's
    unguarded UPDATE wisps SET is_blocked = 0, silently restamping wisps.updated_at across the plane,
    unrecoverable because the wisp plane has no committed history. Upgrades now apply exactly the pending
    set, ignored/00120026.
  • Server-mode DOLT_ADD/DOLT_COMMIT run 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 bootstrap probes git remotes for Dolt data instead of rejecting them, and exits non-zero when
    it declines
    (#6037; fixes #5743, #5663). Bootstrap rejected the very git-origin-derived remote
    bd init persists, printed ✓ Database already exists for the refusal, and exited 0 with nothing
    created — a closed loop with bd init --reinit-local pointing 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; every USE
    failure was labeled database not found, including access denials. Both are fixed, along with a
    silent-data-hazard variant worse than the reported error, and --init-if-missing is honored in
    proxied-server mode.
  • --storage-class is 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
    --file markdown batches, and on --graph plans; 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=5 stores the typed scalar 5 (restoring
    v1.2.2's inference); and bd create --json timestamps carry nanosecond precision again.
  • A Homebrew --HEAD version stamp is recognized instead of being misread as 0.0.0 (#6079,
    carrying @anisoptera's #5625; refs #5603, #5650). Version comparison scans dot-separated parts with
    %d and 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 .dolt when the
    .bd-dolt-ok marker is absent. Two stamps reach that call: HEAD-<shortsha> from
    brew install --HEAD, and the v1.1.1-0.2026… Go pseudo-version that bricked five production cities
    in #5650. The recovery is now gated on IsValidSemver before the comparison, so it can only remove
    predecessors from the recovery set, never add one. Alongside it: bd doctor reports a --HEAD build
    as healthy instead of nagging brew upgrade beads (which would undo what the user asked for), a
    changed HEAD stamp counts as an upgrade so the post-upgrade reconciliation runs, and
    bd upgrade status/review yields no delta rather than dumping the entire release history.
  • bd flatten / bd compact run 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 --full is new, and a low-reclaim
    pass now hints at it.
  • bd no 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
    takes GET_LOCK only on the path that can actually migrate; it issues no writes and fails closed.
  • bd purge and bd prune select candidates by tier (#5995). A wisp minted before the ephemeral
    column was set belonged to neither sweep — one production database had 858 rows no sweep could ever
    reach. The tiers are now complementary predicates, so the first purge after upgrade may clear
    considerably more than usual
    .
  • Server mode honors Config.LenientOpen (#5783 by @Toady00; fixes #5781). The dirty-table
    refusal's documented recovery — bd dolt commit — could not itself open a server-backed store, a
    deadlock previously broken for embedded mode only. Found in the wild: 15 of 21 databases on one
    deployed server carried a permanently dirty config table.
  • The aux-row rekey survives dolt#11131 encoding drift (#5064; fixes #4380). A drifted
    events/comments table panicked Dolt inside the re-key scan, aborting the migration and leaving the
    database unopenable. Drifted tables are now skipped with a warning, recorded clone-locally in
    aux_row_rekey_drifted, and re-keyed on a later pass; healing the drift itself is Dolt's
    schema-encoding-drift recover-rows. This already reached 1.1.x and v1.2.2 users — it is the
    [1.1.2] fix — and lands on the main line here, so a store upgrading from v1.2.2 is not newly exposed.

    It is load-bearing for this upgrade specifically: the v53 → v66 chain runs a second aux-row rekey
    pass through the same drift-protected code path when a v53 store crosses main-series v61, and at this
    tag the dirty-table exemption is computed per-pass and scoped to exactly the tables the upcoming
    rewrite will touch, so a post-marker resume cannot sweep a non-drifted table's pre-existing user edits
    into the migration commit. Diagnosis and fix by @marcodelpin, carried and reworked by @maphew.
  • Incremental auto-export actually takes the incremental path (#5806), and three format/scope
    regressions the dead code path was hiding — leaked memories, included owner issues, a missing _type
    discriminator — are pinned by tests. Server-mode only; embedded mode still full-exports every cycle.
  • Disabling telemetry no longer strands the queued metrics backlog forever (GH#5712) — 2M+ files /
    15.8GB observed on one control VM; the prune child now runs, network-free, until the backlog decays.
  • Quality-of-life truth-telling: bd prime says when the memory plane could not be read instead of
    impersonating an empty one (#5877); bd show on a deleted or purged bead points at bd history <id>
    instead of printing text identical to an ID that never existed (ga-m6inyb); bd vc commit /
    bd dolt commit sweep the entire working set and report Nothing to commit honestly; 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 show no longer corrupts quoted shell globs via CommonMark
    emphasis pairing (#5799); and bd blocked no longer silently ignores the label filters it already
    accepted (#5822).

Security

First shipped on a tested release here — the withheld [1.2.1] work

  • bd dolt push and bd sync no longer adopt a git-origin-derived Dolt remote without consent
    (#5068). On a rig with no Dolt remote configured, both commands silently derived one from
    git remote get-url origin, added it, persisted sync.remote into .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/-y consents ahead of time for scripted use; --no-adopt or BD_NO_REMOTE_ADOPT=1 disables
    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 kv keys
    and the bd remember memories nested under them are user data stored as config rows — they ride in the
    settings table because there is one table, not because they are settings — and both doors onto the
    settings plane were listing them with their values. On the HTTP door that meant an unauthenticated
    GET /v0/beads/config handed 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 serve still serves every memory in full through /v0/beads/memories by design.
  • The no-ID "last touched issue" fallback is interactive-only (bd-m00pb, #4839). A scripted
    bd update $ID … with an accidentally empty $ID silently 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 serve is only ever a loopback developer tool, and both
    flags default to today's behavior so a bare bd serve is 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
    bd binary
    : quadratic net/url path resolution, html/template JavaScript-regexp context tracking,
    unbounded post-handshake crypto/tls messages, a missing ReadHeaderTimeout on net/http's
    unencrypted HTTP/2 check, unbounded recursion in encoding/xml and encoding/asn1, and the
    x/net/idna Punycode bug in net/http's vendored copy — bd serve is a real HTTP server and bd makes
    outbound TLS calls. The go directive 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/crypto 0.55.0, golang.org/x/mod 0.40.0,
    klauspost/compress 1.18.7, moby/go-archive 0.3.3, kin-openapi 0.144.0 with oapi-codegen 2.7.1,
    and a refreshed beads-mcp/uv.lock. None of the fixed code is reachable from the shipped binary — the
    headline critical is kin-openapi's openapi3filter middleware, which enters the graph only through
    the tool directive 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
    b3ef65c8 is 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/bd liveness,
    worktree-remove boundary), and nix 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
    TestSharedServerDatabase in both directions. The other three were harness bugs: a jsonOutput global
    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 both show suites (#6086).
  • Upgrade smoke ran the five legs that matter for this release — v1.1.0, v1.1.2, v1.2.1, v1.2.2 and
    v1.2.2-rc.1 → candidate — alongside 14 historical upgrade lanes from v0.9.1 through v1.2.2.
  • A new wisp-plane upgrade corpus (#6049) seeds and asserts the clone-local plane the 1.3.0 migration
    chain rewrites — wisps, wisp deps and comments, leases, events, and the ignored-track cursor — across
    v1.0.1/v1.1.x/v1.2.2 → candidate, closing the coverage hole behind this release's sentinel-replay and
    restamp fixes.
  • The v53 → v66 upgrade, the shared-server consent flow, the backup-first recipes, and the multi-clone
    designated-migrator flow are documented
    in
    Upgrading, with the cursor-rollback procedure in
    the recovery runbook.
  • RC tag gates, all green. The v1.3.0-rc.1 release pipeline completed with
    verify-version-consistency, both goreleaser legs (linux + darwin), both package gates (MCP, npm), and
    release attestation successful, and both registry publish jobs skipped by prerelease gating. The
    tag-triggered Migration Test Harness and Cross-Version Smoke runs both passed. The published
    linux_amd64 archive checksum-verifies and reports 1.3.0-rc.1 (9c6a69ec1). A local validation battery
    against the tag passed the full release stability gate — all 7 upgrade scenarios from each of v1.2.2,
    v1.1.2, and v1.1.0, including the new wisp-plane leg — plus fresh-workspace, migration-UX (28 steps,
    counter restart as documented), forward schema-skew guard, migration-freeze, and journal-actor checks.

Installing

For the v1.3.0-rc.1 prerelease, take the binary from this release's assets — the package managers
below track the latest stable release and will not pick up a prerelease. For the final v1.3.0, use
the command that matches your install method:

# macOS / Linux / FreeBSD
curl -fsSL https://raw.githubusercontent.com/gastownhall/beads/main/scripts/install.sh | bash

# Homebrew (macOS, Linux)
brew upgrade beads

# npm
npm update -g @beads/bd

# go install — server-mode only
CGO_ENABLED=0 go install github.com/steveyegge/beads/cmd/bd@latest

# go install — embedded-capable
CGO_ENABLED=1 GOFLAGS=-tags=gms_pure_go go install github.com/steveyegge/beads/cmd/bd@latest
# Windows
irm https://raw.githubusercontent.com/gastownhall/beads/main/install.ps1 | iex

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

brew uninstall bd
brew untap gastownhall/beads 2>/dev/null || true
brew untap steveyegge/beads 2>/dev/null || true
brew install 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.LenientOpen so 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 --HEAD version stamps was carried
    as #6079, keeping a HEAD build out of the .dolt-deleting pre-v56 recovery

Recommended Reading

Full Changelog: v1.2.2...release/1.3.0