github gastownhall/beads v1.3.0-rc.1

pre-release5 hours ago

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.
v1.2.2 was a
recovery release that re-shipped the v1.1.2 code under a higher version number,
so a v1.2.2 user is running 1.1-era code and meets everything below at
once: the changes listed here, plus the [1.2.1] changes that release
deliberately withheld. Read the upgrading notes before installing.

Highlights

  • 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).
  • 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).
  • 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.

  • 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.
  • 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.
  • 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. Use bd kv /
    bd remember instead.
  • The published backend package drops orphan handling (bd-gwryr). A Go
    consumer that names backend.OrphanHandling or its constants no longer
    compiles.

New in 1.3.0 (full entries under Changed):

  • 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

  • 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 issues.
  • 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

  • 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

  • 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 issue's record, refused again on every
    subsequent command, and the documented recovery re-imported the JSONL —
    resurrecting the issue 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.
    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 issue 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 issues
    (#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

  • 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
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
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
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

Don't miss a new beads release

NewReleases is sending notifications on new releases.