Skip to content

chore(deps): update dependency gastownhall/beads to v1.3.0 - #56

Open
renovate[bot] wants to merge 1 commit into
mainfrom
renovate/gastownhall-beads-1.x
Open

renovate[bot] wants to merge 1 commit into
mainfrom
renovate/gastownhall-beads-1.x

Conversation

@renovate

@renovate renovate Bot commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

ℹ️ Note

This PR body was truncated due to platform limits.

This PR contains the following updates:

Package Update Change
gastownhall/beads minor 1.2.21.3.0

Release Notes

gastownhall/beads (gastownhall/beads)

v1.3.0

Compare Source

The first tested release off main since the 1.1 line. [1.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 upgrade notes before installing.

Upgrade 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): migrating it
promotes the schema for every client at once, so it waits for explicit consent
via bd migrate schema. Remote-backed stores keep their existing
designated-migrator gate. Upgrade all of that server's clients first — see
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 existing
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.

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 further down plus the
Changed section here — read [1.2.1] in full before upgrading, since
[1.2.2] withheld it and v1.2.1 itself was pulled, so nobody on the supported
line has seen it.

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

Carried from [1.2.1] — never shipped to v1.2.2 users:

  • bd update --status <done-status> now enforces close policy. Moving an
    issue to closed (or any configured done-category status) via bd update,
    bd batch update, or the issueops facade refuses when the issue has open
    children or a live direct blocker, matching bd close. Override with
    bd update --force (update <id> status=closed force=true in bd batch;
    UpdateRequest.ForceClosePolicy for facade consumers). An unforced refusal
    rolls back the entire batch.
  • bd search includes closed issues by default (bd-t5yex). The dominant
    query is "was this already found/filed/fixed?", where excluding closed issues
    produced a false "no". Narrow with --status open to get 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
    (bd-m00pb,
    #​4839). A scripted
    bd update $ID … with an empty $ID now refuses in argument validation
    instead of mutating whatever was touched last. The fallback requires a
    terminal stdin; BD_NON_INTERACTIVE=1 and CI=1/true also disable it. 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). It previously passed --status through
    unvalidated and showed closed beads. A --status typo is now an error rather
    than an empty list. Every bead type still shows.
  • bd dolt push and bd sync no longer adopt a git-origin-derived Dolt
    remote without consent
    (#​5068). Both used to silently derive a remote from
    git remote get-url origin, persist sync.remote, commit that config change
    under your git identity, and upload the full issue history — which published
    a whole issue database to a public origin on a command the user believed
    targeted an already-configured remote. 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.
    On a Dolt SQL-server workspace the flag was previously a
    silent no-op and the server came up fully writable, so 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 GET /v0/beads/config handing out every stored memory. A
    script that read memories out of bd config list now gets empty output rather
    than an error; use bd kv / bd remember instead.
  • The published backend package drops orphan handling (bd-gwryr).
    backend.OrphanHandling and its four constants, the storage originals they
    aliased, BatchCreateOptions.OrphanHandling and issueops.CheckOrphan are
    removed. They never did anything except cost a query, but a Go consumer that
    names them 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, so
    upgraded workspaces and fresh clones converge on the same shape), stamped
    inside the mutating transaction with the same identity the audit-events table
    resolves; on a comment row it is the comment's author. bd events tail /
    bd events export JSON gains an additive, omit-when-empty actor field —
    empty means the path had no actor (derived maintenance, deletes, rows older
    than the column), never a user. The input for same-field / different-actor
    conflict measurement over the journal.

  • bd warns when it stores a label containing a space
    (#​5813). Such a label is a
    legitimate thing to ask for — -l 'good first issue' — and bd stores it as
    asked rather than re-splitting a boundary the shell already decided. But it is
    also exactly what a missed comma looks like, and that case cannot be told
    apart from the deliberate one at the point of writing. A line on stderr
    catches it at the keystroke instead of months later: ⚠ Stored "auth backend" as ONE label — it contains a space. It is advice, not an error; stdout is
    untouched, so --silent still prints just the ID, and --quiet silences it.
    Not emitted for --remove-label, since removing such a label is how the
    damage gets repaired.

  • bd doctor gains a Label Whitespace check for finding damage already in
    a database (#​5813). It
    reports labels that contain whitespace or differ from their trimmed form, and
    bd doctor --fix repairs them. The check is warn-only: a database with legacy
    damage still exits 0. As with the rest of bare bd doctor, it does not yet
    run in embedded mode (GH#3794).

  • bd stale and bd blocked gain --label, --label-any and
    --exclude-label
    (#​5822),
    with the same meanings and the same flag names they already have on bd list
    and bd ready: --label is AND (repeatable, must have all), --label-any is
    OR, --exclude-label drops anything carrying one. They were the two listing
    commands with no label-scoped output at all, so a theme-scoped stale sweep had
    to pull the whole list as JSON and post-filter it on the labels array.
    Filtering happens in SQL ahead of LIMIT, not on the hydrated rows, so
    --label x --limit 10 returns ten matching issues rather than whatever
    survives filtering the first ten. No --theme flag: beads has no theme
    concept, themes are labels here, and --label theme:x is the native way to
    say it.

  • New *.gate.lock files appear next to and inside .beads (#​5046,
    #​5093). A two-level
    cooperative gate now serializes the operations that cannot safely overlap —
    ordinary commands take it shared, maintenance like bd backup restore takes
    it exclusive. It is flock-based, so the file is the lock's name, not its
    state: the gate is created once (mode 0600) and deliberately never deleted,
    because deleting it is how two processes end up holding two different locks
    for the same resource. Each gate sits beside what it guards, never inside
    it: the workspace gate for <dir>/.beads is <dir>/.beads.gate.lock in the
    project root, and the physical-root gate is .beads/embeddeddolt.gate.lock in
    the default embedded mode (.beads/dolt.gate.lock under a server layout;
    under the shared server it lives beside ~/.beads/shared-server/dolt and
    never enters the workspace at all; a remote Dolt host has no physical gate).
    Both are covered by the *.gate.lock* pattern bd doctor maintains in the
    project and .beads gitignores, so an upgraded workspace picks up the ignore
    rule on the next bd doctor --fix; a workspace with no .beads directory is
    not gated at all, rather than scattering lock files into whatever directory
    bd was run from.

  • sort on GET /v0/beads/issues. The listing served one order —
    (created_at DESC, id ASC) — because the cursor is a keyset position in it,
    and the spec said so in its own words: "the sort order is welded to the cursor
    contract, and a new order needs new surface." The cost of that welding fell on
    every client that wanted bd list's ordering, because the only way to get it
    was to page the whole result set and re-sort locally. Measured over a
    1400-row store at the 200-row page an HTTP client actually uses, that is
    7 requests plus a client-side comparator; ?sort=priority is 1
    (TestProxiedServerListSortRetiresTheWalk, which runs both strategies against
    a real bd serve and checks both against bd list --json row for row). It is
    the one cost on this surface that got worse as a project grew.

    Two values, and the set is closed. created is the existing order, now
    spellable. priority is (priority ASC, created_at DESC, id ASC)bd list's flagless ordering, which is also what bd list --sort priority
    produces, so one served order retires the walk for both. The other seven
    orders bd list --sort takes are not offered: each value here is a cursor
    contract needing a key proven total, and id is a natural-numeric order no
    database expresses, updated moves on every write, closed is nullable, and
    status/title/type/assignee are mutable and unindexed.

    Absent sort still means created, permanently. It is the compatibility
    contract for every client written before the parameter existed; changing it
    would alter which rows a truncated page contains, with no error to notice it
    by. A server that predates the parameter answers param: "sort" with
    reason: "unknown_parameter", which is the per-parameter capability probe a
    client dispatches on to fall back.

    The cursor now carries its order, and a mismatch is refused. This is the
    half that makes the parameter safe rather than merely useful. The token was
    base64 of {t,i} with no binding, so a created-order cursor replayed under
    sort=priority would have decoded perfectly and been read as a position in a
    different total order — a page that both skips and duplicates rows, served
    with a 200, undetectable by the client. Tokens are now v2 with an explicit
    order member and decode refuses any token whose order differs from the
    request's, as invalid_cursor (documented recovery: restart paging).
    Outstanding v1 tokens stay readable as the created-order positions they
    are, so no traversal in flight has to restart. This does not contradict
    "the token carries no filters": filters select the set, the order decides what
    the position means.

    priority is a mutable key, which created_at is not, and the spec says
    so: under created only new rows move relative to a walk, while a priority
    update moves an existing row too, so it can be seen twice or missed. That is
    the already-documented "a cursor pins a position, not a snapshot" caveat
    reached by a second route, not a new class of error — unchanged data never
    skips or repeats under either order.

Changed
  • Shared Dolt databases (sql-server mode) no longer auto-apply schema
    migrations on a version bump. Migrating a shared database promotes the
    schema for every connected bd client at once and locks out clients still
    on an older binary, so bd now refuses without explicit consent: run
    bd migrate schema once after upgrading every client, or set
    BD_ALLOW_REMOTE_MIGRATE=1 in scripted use. Reads keep working on the
    current schema meanwhile. Embedded (single-user) databases still
    auto-migrate silently, unchanged. (#​5920)

  • The smart migration gate's "auto-migrate as safe first-mover" and
    auto-fast-forward arms are now embedded-only. On a shared server the gate
    always stops for consent, because neither argument can observe the other
    clients attached to that server. (#​5920, #​4516)

  • The newly exported Go packages (backend, backend/conformance, beadserrors,
    issueops, journalops, memoryops, schema, test/conformance) are marked
    EXPERIMENTAL: they are not yet covered by the project's compatibility promise and
    may change in a minor release. Pin an exact beads version and re-run the
    conformance suite on every bump.

  • The optimistic-concurrency token is now a JSON string — responses carry
    revision as an opaque decimal string and expected_version must be sent as
    that string. JSON-number tokens exceeded JavaScript's 2^53 integer precision.
    This member never shipped in a tagged release.

  • CI: migration-test and upgrade-smoke now seed and assert wisp-plane data
    (wisps, wisp deps/comments, leases, events, ignored-track cursor) across
    v1.0.1/v1.1.x/v1.2.2 → candidate upgrades.

  • --profile is now --cpu-profile, with no alias
    (#​5126, bd-ugz). The
    persistent flag that writes a CPU profile is spelled --cpu-profile; the old
    --profile spelling is gone rather than deprecated, so it fails as an unknown
    flag instead of silently doing nothing. --profile is the conventional name
    for a named configuration, not for pprof output, and holding it hostage to
    a debugging aid nobody passes twice a year was the wrong trade. Scripts and
    aliases that profile bd need the new spelling.

  • Migration 0061 rekeys every events, comments, snapshot and
    compaction-snapshot row to a content-derived id

    (#​5150, bd-ri8bd). Those
    four aux tables minted random UUIDv7 ids, which is fine for a single writer
    and wrong for replication: unversioned newest-wins replication has no merge
    pass, so two clones recording the same fact produced two rows that never
    converged. Ids are now UUIDv5 over a SHA-256 digest of the row's own content
    (frozen column order, plus the table name and a duplicate-disambiguating
    ordinal), so the same fact derives the same id everywhere and convergence is
    a property of the key rather than of a reconciliation step. New rows derive
    their id at insert; existing rows are converted by a one-time bulk rekey
    on the first open after upgrade, which is the main reason that invocation is
    slower than the ones after it. The pass is crash-resumable through a
    local_metadata sentinel and records completion clone-locally, so it runs
    once per clone and is not repeated on the next command. wisp_events and
    wisp_comments are deliberately excluded — they are clone-local and never
    merge. 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, bd-red8u).
    Migration 0062 moves events onto the dolt_ignored plane in a self-committing
    drop/commit/recreate that preserves every row. Events are single-writer audit
    rows with low cross-machine value, and minting a versioned Dolt commit for
    each one was the dominant source of commit churn on a busy store. The
    consequence is the point: events rows no longer replicate. They keep
    full SQL durability locally and bd history <id> --events still reads them,
    but a pull no longer brings another machine's events over, and a push no
    longer sends yours. Events already committed before the flip stay in the
    remote's history until the next squash window. Comments stay versioned.

  • The interactions.jsonl audit sidecar is opt-in
    (#​4688, fixes
    #​4687). audit.enabled
    now defaults to false and bd init no longer creates the file, so
    status/assignee/priority field-change logging writes nothing unless you ask
    for it. Set audit.enabled=true (or BD_AUDIT_ENABLED=1) to restore the old
    behavior. The database-backed replacement is bd history <id> --events, which
    needs no sidecar. Anything that read .beads/interactions.jsonl on the
    assumption it would exist needs the config key set.

  • bd import refuses a redirected stdin rather than quietly ignoring it
    (#​5171, bd-axluy). Bare
    bd import imports the default JSONL, and bd import - imports stdin; a
    bd import < file.jsonl looked like the second and behaved like the first,
    importing a different file than the one on the command line with no way to
    notice. With stdin redirected, no -, and no named source, bd import now
    errors and names both fixes. /dev/null is a character device, so a scripted
    bare bd import under a subprocess still imports the default file as before.

  • Auto-backup defaults to off under a Dolt sql-server (wy-zrmqr). The
    automatic backup used to switch itself on whenever a git remote existed,
    regardless of storage mode — which on one shared sql-server meant ~31 clients
    independently deciding to back up the same database, pinning its CPU. The
    default is now off in server, proxied-server and shared-server modes (and in
    nocgo builds, which are always server-backed); embedded mode is unchanged and
    still enables it when a git remote is present. An explicitly configured
    backup.enabled (or BD_BACKUP_ENABLED) always wins over the default, and
    bd config get backup.enabled now prints the effective value with its source
    default (auto: off in sql-server mode) — rather than a bare false that
    could not be told apart from a configured one.

  • bd hooks install --chain and --force are accepted no-ops
    (#​5284, bd-5vdt8). Managed
    BEGIN/END BEADS INTEGRATION marker sections made both flags meaningless:
    an install now replaces only the content between its own markers, so content
    outside them is always preserved (what --force used to negate) and an
    existing hook always keeps running alongside the bd section (what --chain
    used to request). The flags remain registered so existing invocations do not
    break, and --json still echoes them, but neither changes what is written.
    A hook with no markers is handled by shape: a legacy bd hook is replaced, and
    a foreign one gets a one-time <hook>.backup sidecar before the section is
    injected. Symlinked and git-tracked hook paths are refused outright, before
    anything is written.

  • Write commands refuse to run while a migration freeze marker is present
    (dc-6jaq). When a file named MIGRATION-FREEZE exists in the workspace
    directory, the working directory, or any ancestor of either — or at the
    path named by BD_MIGRATION_FREEZE_FILE, which is authoritative when set
    bd create/update/close/remember/import and every other write
    command (~120 call sites, bd q included), plus the destructive
    bd init --reinit-local and bd bootstrap, print ⛔ workspace is frozen for migration naming the marker's path and exit 14, a distinct code
    scripts can branch on, instead of writing to a store mid-migration.
    Operators freeze a workspace by creating the file (optionally
    operator<TAB>RFC3339 timestamp<TAB>reason on one line, echoed back in
    the refusal) and thaw it by removing the file. Read commands (list,
    show, ready, …) keep working during a freeze, and bd's own maintenance
    stands down with them — version tracking, 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. If bd
    cannot tell whether a marker is present, it refuses rather than assuming
    the workspace is open.

  • bd dep add names the implicit type=blocks default, but only to an
    interactive operator
    (#​5854). Creating an edge with no -t/--type silently
    produces a blocks edge, which drops the dependent out of bd ready — the
    usual surprise when someone meant structural parent/child linkage. bd dep add now says so on stderr. Because blocks is the documented default and
    the majority-correct case, the note is gated: it is emitted only when stderr
    is a TTY, so scripted and agent callers (and CI, and bd dep add 2>log)
    never see it and are not trained to ignore stderr. --quiet silences it, as
    it does the other non-error stderr notices, and BD_NO_DEP_TYPE_WARNING=1
    turns it off for operators who have internalised the default. --json output
    is unchanged.

  • bd show labels created_by as Created by:, not Owner: (be-ss66).
    The text view rendered created_by under a label naming a different
    concept. owner is a separate field holding a separate value — the git
    author email kept for CV attribution — exposed as owner in --json and
    never rendered in the text view at all. Two identities, one label, and the
    label named the one that was not on screen. This is a correctness fix and
    not polish: a reader asking "whose bead is this" reasonably reaches for
    what the label promises, and two defect analyses were filed off exactly
    that mistake in one evening, both measuring provenance from the wrong
    column and both retracted after another agent disproved them. bd show
    now prints Created by: <identity>. The --json payload is unchanged, and
    a repo-wide audit found no in-tree parser of the old Owner: text — the
    only other producer, bd github's repo-config display, names an actual
    repository owner and keeps its label.

  • beads_dir and repo_root are now OPTIONAL members of ContextResponse
    on GET /v0/beads/context.
    They are the only two members of the identity
    handshake that describe the SERVER's filesystem rather than the workspace's
    logical identity, and an absolute host path is not something a remote client
    can act on — it cannot open it. The document already called publishing them
    a cost "an operator accepts when binding beyond loopback"; while they were
    required, accepting it was the only conforming option. A deployment that
    does not want to disclose its filesystem layout can now withhold them and
    still serve a conforming body.

    No body changes. bd serve publishes both, always, exactly as before —
    the relaxation is a promise to clients, not a switch on the server. What
    changes is what a client may assume: it MUST tolerate their absence, and
    MUST identify a workspace by project_id, database and backend, which
    stay required. In Go, apigen.ContextResponse.BeadsDir and .RepoRoot
    become *string.

  • An explicitly configured Dolt server port now outranks the ambient
    BEADS_DOLT_SERVER_PORT/BEADS_DOLT_PORT environment variables

    (be-wf9a.1, be-9tju, GH#4052). ServerPort resolution now carries a
    source — which step of the chain produced the port — and the env read only
    applies when that source is not an assertion by the user or by tooling acting
    for them. Two deliberate behavior changes follow. First, a port set by the
    caller before the store opens (a library caller filling in Config.ServerPort,
    or bd init --server-port) is no longer silently replaced by whatever the
    surrounding shell exported; previously the env var won unconditionally.
    Second, the legacy BEADS_DOLT_PORT spelling no longer overrides a port
    resolved from .beads/config.yaml (dolt.port), Dolt's own config.yaml
    (listener.port), or metadata.json (dolt_server_port). The current
    BEADS_DOLT_SERVER_PORT spelling is unaffected — it sits first in the
    resolution chain and still wins over all of them.

    Compatibility: a workspace that relied on exporting BEADS_DOLT_PORT to
    redirect away from a port pinned in config now connects to the pinned port
    instead. Switch to BEADS_DOLT_SERVER_PORT, or change the pinned value. Ports
    that bd resolved for itself — the gitignored .beads/dolt-server.port, and
    the shared-server default 3308 — stay non-authoritative and remain overridable
    by either spelling, which is the case that kept working throughout.

    A related failure goes away with it: because bd's own bookkeeping is no longer
    mislabeled as user configuration, a stale .beads/dolt-server.port left by a
    crashed server no longer makes auto-start refuse to start with "bd will not
    silently use a different port than the one you configured"
    when nothing was
    configured. Auto-start warns and retargets, as it did before. Shared-server
    mode still fails closed there — a repo-local auto-start is a different
    database, not a port refresh — but now says so in its own words.

  • bd reclaim summarizes the leases its replica guard declined instead of
    naming every one, every run
    (wy-sp2l4). A lease granted by another replica
    is by construction never reclaimed here, so the audit was not a one-off: it
    repeated identically on every run, one stderr line per stranded remote lease.
    A supervisor on a 1-minute timer against a federated store with 47 of them
    printed 47 lines a minute, indefinitely. The default is now ONE line —
    reclaim: skipped 47 stale leases granted by other replicas — "mini" (30), "mini2" (17), not this node ("laptop")… — naming at most three replicas and
    collapsing the rest into a count, with the exact total preserved. bd -v
    (or BD_DEBUG=1) expands it to the per-lease lines, itself capped at 20 with
    a collapsed tail. Still stderr-only: bd reclaim --json owns stdout and its
    payload is unchanged. Under --quiet the audit now skips its queries
    entirely rather than running them to discard the output.

  • Actor matching decodes an exact -- run to / instead of collapsing it to
    a generic separator
    (be-p7dzx, bundled be-vc51). Identity comparison — used
    by ExpectedAssignee on release and update, and by claim/close/unclaim
    authorization — canonicalizes a run of ., _ or - to one separator so the
    same identity spelled under different layers' conventions compares equal. An
    exact two-byte -- run is now excluded from that collapse: it is gascity's
    session-name encoding of a rig-qualified agent's /, so it decodes to /.
    Compatibility: gastown--mayor now matches gastown/mayor, and stops
    matching gastown__mayor and gastown-mayor. That is the intended fix rather
    than a side effect — gastown--mayor is the agent mayor on rig gastown
    while gastown__mayor is the dotted alias gastown.mayor, so matching them
    was a widening — but any stored -- spelling that is NOT a gascity slash
    encoding changes equivalence class silently, with no error to notice. Longer
    or mixed runs, __ and --- included, are unaffected and still collapse.

  • bd ready --claim now refuses a row cap under --proxied-server. The
    proxied ready role cannot enforce --max-rows/BEADS_MAX_ROWS, so bd fails
    loudly rather than silently dropping the limit; --claim no longer exempts
    the command. Agent rigs that set the cap globally must unset it for proxied
    bd ready --claim. Direct mode is unchanged, and a claim there still
    succeeds against a ready pool larger than the cap. (#​6269)

Fixed
  • Server-mode issue mutations no longer revert concurrent writers' committed
    rows
    (#​5740). The
    issue-mutation path ran DOLT_ADD/DOLT_COMMIT inside its still-open SQL
    transaction; DOLT_ADD stages the whole table as of the transaction's
    BEGIN-time root, and the Dolt commit was minted before the commit-time merge
    that reconciles concurrent writers — so a mutation could silently write every
    row another session had committed during its window back to that session's
    BEGIN-time value, seen in production as claims reverting minutes after they
    were made. The Dolt commit now runs after the SQL transaction commits, against
    the post-merge working set. Its failure is logged rather than returned: the
    data is durably committed by then and rides the next Dolt commit, and treating
    an applied mutation as failed is what made retries double-apply and claim
    verification disown claims the caller held. Reported and fixed by
    @​nova-submodules.

  • Removed-backend rejection now detects an existing Dolt database and gives
    the exact metadata.json heal (change "backend" to "dolt", or delete the
    field) instead of a destructive export/reinitialize path. Workspaces that bd
    v1.2.x opened as Dolt despite a stale "backend" value are one edit from
    healthy, and the message now says so. Rejection remains fail-closed and
    read-only — no storage database is opened or modified — and still exits 1.
    (Detection helper ported from @​steveyegge's #​4740.)

  • bd migrate no longer aborts with a duplicate-primary-key error when the same
    dependency edge exists in two typed target columns; duplicate edges are now
    merged deterministically (issue-ref wins over wisp/external), renaming an issue
    no longer leaves its dependency rows on a stale primary key, and databases left
    half-rekeyed by earlier binaries are detected and repaired on the next
    migration pass (#​5268).

  • Legacy tracked ignored_schema_migrations self-heals by untracking it at
    open time, unwedging bd dolt pull on upgraded databases (#​4356).

  • A v1.1.x/v1.2.x ignored-migration cursor is believed when
    leases.granted_node is merely not yet migrated, so upgrades apply only the
    pending ignored tail instead of replaying ignored/0007 and silently
    restamping wisps.updated_at (#​5981 class, #​5366 follow-up).

  • Auto-export heals after bd delete instead of permanently refusing — the
    orphan guard proves deletions against the store's own history (embedded and
    server modes), stores wedged by earlier binaries recover on first run, and
    export-state.json is now versioned (#​5896).

  • bd flatten and bd compact now finish with a full Dolt GC (all
    generations), so re-running them on a previously-GC'd store actually reclaims
    the orphaned history and retires orphaned --as-of addresses; bd gc gains
    --full and hints when a default pass reclaims little (#​5907).

  • --storage-class is honored on the proxied-server and --file routes and
    rejected as a plan-wide --graph flag; it was silently ignored there.

  • Delete and cascade dep_remove journal rows are attributed to the
    requesting actor
    (#​5985); bd events export/tail now say when the journal
    is disabled.

  • Truthful errors when a server-scoped credential cannot reach the requested
    database
    (missing / denied / wrong-session attribution); --init-if-missing
    is honored in proxied-server mode.

  • The aux row re-key survives dolt#11131-class encoding drift instead of
    failing mid-upgrade with a raw invalid hash length error. Drifted tables are
    skipped with a warning, recorded clone-locally in aux_row_rekey_drifted, and
    re-keyed on a later pass, rather than leaving the database unopenable. 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
    (#​5064, #​4380). Diagnosis and fix by @​marcodelpin, carried and reworked by
    @​maphew.

  • Server mode honors Config.LenientOpen, so the dirty-table guard's
    documented recovery is executable. The refusal tells you to run
    bd dolt commit / bd vc commit, but against an external Dolt server those
    commands could not open the store to do it — the flag reached server mode and
    newServerMode never read it, while the field's own doc comment said "Ignored
    in server mode". That left the #​4566 deadlock intact for exactly the case with
    no way out: embedded mode can move the database directory aside and
    bd bootstrap, and there is no equivalent against a shared server. Fixed by
    @​Toady00 (#​5783, #​5781).

  • bd bootstrap probes git remotes for Dolt data instead of rejecting them
    outright
    (#​6037, #​5743, #​5663). bd init deliberately persists a
    git-origin-derived sync.remote into the tracked .beads/config.yaml — a git
    origin is a valid Dolt remote before refs/dolt/data exists, because the first
    bd dolt push creates that ref on the same remote — but bootstrap classified
    those URLs as code-repo URLs and returned before the origin auto-detect that
    would have hydrated the clone, git+https:// and git+ssh:// included.
    Committed sync.remote values from earlier releases hydrate again. The failure
    was silent because one outcome code covered both "a database already exists,
    nothing to do" and "I refused and left you with nothing": bootstrap printed
    ✓ Database already exists, exited 0, and created nothing, after which
    bd init --reinit-local pointed back at bd bootstrap in a closed loop.
    Bootstrap now exits non-zero whenever it declines to set up a database.

  • --set-metadata again stores numbers/booleans/null as typed JSON scalars
    (v1.2.2 behavior); string-forcing lives on --metadata-json. Values written by
    in-window dev builds are left as strings; re-apply the flag to retype a key.

  • bd create --json (and POST /v0/beads/issues) again echo sub-second
    RFC3339Nano timestamps
    ; reads (show/list) remain second-precision as in
    v1.2.2.

  • bd purge and bd prune select candidates by TIER, so a typed wisp is
    ephemeral however it was minted

    (#​5995). The two sweeps
    split the world between them — bd purge clears the ephemeral tier, bd prune the durable one — but membership was decided by the raw ephemeral
    column, and a wisp minted before that column was set carried a wisp_type
    with ephemeral = 0. Such a bead belonged to neither sweep: bd purge
    reported "No closed ephemeral beads to purge" while bd prune skipped it as
    a wisp. One production database had 858 such rows / 7.3 MB that no sweep
    could ever reach. The tiers are now complementary predicates over both
    columns — ephemeral is ephemeral = 1 OR wisp_type is non-empty, durable is
    its exact complement — evaluated across both planes rather than taking the
    wisps-plane fast path, because legacy typed wisps live in the issues table.
    A non-empty wisp_type now also implies ephemeral at mint, on every create
    path, so no new bead can land in the gap. A previously unreachable typed
    wisp is now a bd purge candidate
    , so the first purge after upgrade may
    clear considerably more than usual. NoHistory beads (wisps plane, no
    wisp_type) stay durable-tier, unchanged.

  • bd prime says when it could NOT read the memory plane
    (#​5877). A broken or
    unreachable store made prime omit the memory section entirely, so a session
    that woke with zero recall looked exactly like a workspace that simply has no
    memories — same output, same exit 0, no way for a fleet operator to tell them
    apart. Prime still never fails a session-start hook, but a memory read it
    cannot serve now renders Skipped: beads storage unavailable (<error>) — persistent memories were NOT injected this session, alongside the timeout
    banner that already existed for a deadline. Both the classic and the
    proxied-server route share the banner. A workspace-less directory, and a
    healthy store with no memories, stay silent as before.

  • bd no longer serializes every invocation behind the schema-init advisory
    lock.
    The schema-init probe ran entirely inside the database-scoped
    GET_LOCK('bd_schema_init:<db>', 5), so on a shared Dolt server every bd
    process queued behind every other one to discover that it had nothing to
    migrate. Measured on an 18-seat rig: 1500 is_free_lock samples over 14s
    found the lock held 96.7% of the time, held runs had a 673ms median and a
    4.2s max, and free gaps had a 0ms median — the lock was handed straight from
    holder to holder. That queue was 0.4-2.4s of pure waiting on every claim,
    heartbeat, list, comment and mail check. MigrateUpWithLock now answers the
    steady-state question first, without the lock — it puts the session on the
    target database (proving the database exists before issuing any statement
    that could fail, so a fresh bootstrap still falls through to the locked
    CREATE DATABASE; callers whose pool already names the database inject
    nothing and the probe simply declines a session that is elsewhere), then
    asks whether both migration cursors are at this
    binary's latest with the content-hash column and custom-status/type backfills
    done, whether all the canonical dolt_ignore patterns are present, and
    finally whether the migration lock is free — and takes GET_LOCK only on the
    path that can actually migrate. That last term is what keeps the probe honest
    while a peer is mid-pass: the cursors land before the backfills and rekeys
    finish, so a held lock, not the cursors alone, is the proof that nobody is
    rewriting the tables underneath this caller. The probe issues no writes —
    its one session mutation is the USE that pins the target database — and fails
    closed: any error, any unreadable state, and anything short of provably
    converged falls through to the locked path unchanged, as does any caller
    carrying fresh-bootstrap heal authority.

  • Incremental auto-export now actually takes the incremental path
    (#​5806). Change detection
    compared GetStateHash() values — a hash of the entire database plus
    working set (DOLT_HASHOF_DB()), not a commit — but ChangedIssueIDs feeds
    both endpoints straight into dolt_diff(), which only accepts real commit
    hashes or the literal WORKING. Every incremental attempt therefore failed
    to resolve a diff and silently fell back to a full export: correct output,
    but the incremental short-circuit this feature exists for never actually
    ran. The "to" endpoint is now WORKING (dolt_diff's own literal for the
    live working set) paired with the previous export's real commit hash as
    "from", so the incremental path resolves and fires as designed.

    Three format/scope regressions surfaced alongside the dead code path, since
    nothing had ever exercised it end-to-end: the incremental patch could leak
    memories into the auto-export output (full export filters them; the
    patch path did not), could include the configured owner's own issues
    where full export excludes them, and omitted the _type discriminator field
    full export always writes. All three are now pinned by tests that diff a
    patched run's output against a from-scratch full export of the same state.
    The patch write is also now atomic (write-temp + rename), matching the
    full-export path's existing guarantee.

    The owner and _type fixes match full export exactly. Memory records
    deliberately do not
    , and the difference is worth stating: where the full
    path refuses to overwrite an issues.jsonl that already contains memory
    lines (guardAutoExportOverwrite), the incremental path preserves them
    verbatim and proceeds. Preserving is the better behavior — a manually
    seeded export survives patching byte-for-byte instead of wedging — so the
    incremental path keeps it rather than adopting the refusal.

    Scope: this is a server-mode (DiffStore) improvement only.
    EmbeddedDoltStore implements neither DiffStore nor StateHasher, so in
    the default embedded mode the incremental path is inert (incremental skipped — store does not implement DiffStore) and every cycle is still a
    full export. The deletion-proof guard below started out server-mode only for
    the same reason; #​5896 is
    now closed for embedded mode too — see the store-history heal entry below.

    Caveat: the diff anchor only advances on real commits. In server mode
    dolt auto-commit is off, so with no commits being made the anchor stays put
    and the diff range grows with every cycle. That is correct but not free:
    once the change set crosses the 5000-id threshold, every export falls back
    to a full rewrite until something commits and the anchor moves forward.

  • bd delete no longer wedges auto-export in server mode
    (#​5806, part of
    #​5896). Auto-export's
    orphan guard refuses to overwrite an issues.jsonl holding issue records
    absent from the local store — the #​4988 protection against a JSONL that has
    run ahead of the database. A normal bd delete produced exactly that shape,
    so a single delete left issues.jsonl permanently stale, with a "refusing
    to overwrite" warning on every later command. The guard now asks dolt_diff
    whether each JSONL-only id was genuinely removed since the last export's
    anchor and, when proven, stops treating it as corruption.

    The proof is deliberately narrow. It requires diff_type='removed' on the
    issues table itself, so a label-, dependency- or comment-only change, a
    delete-then-recreate, a wisp, or any diff error all still refuse. It also
    requires the anchor to still be reachable from HEAD: after a
    DOLT_RESET --hard, a branch checkout, or any other data-dir rewind, a row
    reads as "removed" without anyone having deleted it, and honoring that
    would drop a live record. Both conditions must hold, or the guard refuses as
    before.

  • Disabling telemetry no longer strands the queued eventsData backlog
    forever
    (GH#5712). bd send-metrics early-returned on disabled metrics
    before its prune step, and the spawn gate refused to schedule the child at
    all when disabled — so the one machine whose queue can never again drain by
    upload (the one that just opted out) kept every queued batch and orphaned
    emitter temp indefinitely (2M+ files / 15.8GB observed on one control VM).
    With metrics disabled, bd now still spawns the detached child under the same
    marker-first throttle, and the child prunes by the normal TTL/cap policy and
    exits without POSTing anything. Until that pre-existing backlog ages out, a
    host that just opted out while its queue still holds batches younger than the
    7-day TTL (and under the file/size caps) keeps forking the throttled,
    detached, network-free prune child every flush interval — reclaiming nothing
    until those batches age past the TTL — so new prune-only bd send-metrics
    processes on a freshly disabled host are expected during that bounded window
    and never POST. Once the backlog has decayed empty, spawns stop entirely; a
    machine that never enabled telemetry has no queue directory and never forks.

  • bd show on a deleted or purged issue no longer prints text identical to
    an ID that never existed
    (ga-m6inyb). bd delete and wisp purge both
    remove a row (and its events) from the live tables with no trace left
    behind, so "not found" was collapsing two states that call for opposite
    responses — archived (success) and never-existed (possibly lost data) —
    into one observable. This had a real cost: a gate-reviewer session showed
    a legitimate issue successfully, hit an unrelated mysql i/o timeout on a
    retry moments later, got the identical "not found" text for the same ID,
    and reasonably concluded data had been lost — when the issue had simply
    been archived in between. bd show on a missing ID now adds a second
    line — Hint: this ID may have never existed, or may reference a deleted/purged record with no trace left in the live database — try 'bd history <id>' — pointing at the existing, opt-in bd history <id> for
    anyone who wants to check further on a non-wisp issue (wisps are never
    committed to history, purged or not, so there is no signal left to
    recover for them). JSON output gains an additive hint field; the
    existing error text is unchanged. Direct-mode text output changes for
    one path
    : the single-ID not-found branch's first line changes from
    Error fetching <id>: no issue found matching "<id>" to Issue <id> not found, matching the wording the multi-ID and proxied-mode paths already
    used (those two are unchanged aside from gaining the new second Hint:
    line).

  • Explicit commits now sweep the entire working set and report no-ops
    honestly.
    In server mode, bd vc commit and bd dolt commit previously
    routed through the config-excluding automatic-commit path, so out-of-band
    config changes could remain dirty even though the command exited
    successfully. Both commands now include config while continuing to exclude
    ignored wisp tables. A clean or otherwise non-committable working set reports
    Nothing to commit (committed: false in JSON) instead of attributing an
    unchanged HEAD to a new commit.

  • Every CLI label write now normalizes its input, as every label filter
    already did
    (#​5813,
    fixes #​5812).
    utils.NormalizeLabels (trim, drop empty, dedupe) was applied to list,
    search, ready, orphans, count and workapi — and to no write path at
    all. The filter trimmed its input and the store did not, so a label could not
    match its own filter: bd create --labels 'theme:a, theme:b' stored
    theme:b with pflag's leading space intact, and bd list --label theme:b
    then returned nothing. The failure is silent in the worst way — a filtered
    list quietly short of rows is indistinguishable from a complete one. One real
    database had 150 such rows across 111 issues, found months later only because
    theme-by-theme counts refused to reconcile. create, quick, update
    (--add-label, --remove-label, --set-labels) and tag now normalize on
    the way in. --remove-label matters as much as the additive flags: an
    untrimmed ' theme:a' silently removed nothing.

    bd tag is included deliberately. Its help calls it "Shorthand for
    bd update <id> --add-label <label>", and it was the last CLI door still
    storing a positional verbatim. Normalization happens before the direct/proxied
    route split, so the two routes cannot disagree. A label that is only
    whitespace is now refused rather than dropped: the plural flags can discard an
    empty element and still honor the rest of the request, but bd tag has one
    label to add, so dropping it would report success having written nothing.

    Import and batch ingest are deliberately excluded and keep round-trip
    fidelity: what was exported is what is restored.

  • bd show no longer corrupts shell globs and other quoted wildcards in a
    description or notes

    (#​5799). CommonMark lets a
    * or _ run act as both an opener and a closer when the characters
    flanking it fall in the same class, and a quoted glob is exactly that shape —
    the * in '*.captured' sits between ' and .. Two such runs therefore
    paired, including across lines, since goldmark joins a paragraph's lines
    before parsing. Everything between them rendered as emphasis: with color the
    asterisks were consumed and silently vanished from the command, leaving a
    still-plausible but different one on screen; without color, glamour's notty
    style wrote a literal ** into the middle of the text. Storage was never
    affected — bd show --json always returned the body byte-for-byte — but the
    rendered text is what a reader acts on, and what an agent reads when it takes
    its instructions from a bead. Runs that can act as both opener and closer are
    now hidden from the parser and restored afterwards, so bd show prints what
    was stored.

    The known trade, in two narrow shapes, both pinned by test: an authored
    closer that is itself both-flanking no longer pairs, so see *"quoted"*, next renders literally rather than italicized (color path only — notty
    already printed it literally); and a backslash escape reaching for a
    both-flanking delimiter keeps its backslash, so \*.captured renders as
    \*.captured. An escape whose delimiter is not both-flanking, such as
    a \*literal\* b, is unchanged. Both are shapes CommonMark itself treats as
    ambiguous, and for a bead body — stored plain text, not authored markdown —
    showing the stored characters is the better of the two failures. Ordinary
    emphasis (**bold**, *italic*), SELECT * FROM t, code spans and fenced
    blocks all render exactly as before.

  • bd blocked silently ignored the label filters it already accepted
    (#​5822).
    types.WorkFilter has carried Labels, LabelsAny and ExcludeLabels all
    along, and bd blocked built one, but GetBlockedIssuesInTx only ever read
    ParentID. Programmatic callers that set them got unfiltered results back and
    no error —

Important

✂ PR body was truncated to here.


Configuration

📅 Schedule: (UTC)

  • Branch creation
    • At any time (no schedule defined)
  • Automerge
    • At any time (no schedule defined)

🚦 Automerge: Enabled.

Rebasing: Whenever PR is behind base branch, or you tick the rebase/retry checkbox.

🔕 Ignore: Close this PR and you won't be reminded about this update again.


  • If you want to rebase/retry this PR, check this box

This PR was generated by Mend Renovate. View the repository job log.

@renovate
renovate Bot enabled auto-merge (squash) September 15, 2026 22:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants