Skip to content

fix(import): preserve JSONL comment IDs#4103

Merged
maphew merged 1 commit into
gastownhall:mainfrom
maphew:fix-jsonl-comment-ids
May 22, 2026
Merged

fix(import): preserve JSONL comment IDs#4103
maphew merged 1 commit into
gastownhall:mainfrom
maphew:fix-jsonl-comment-ids

Conversation

@maphew

@maphew maphew commented May 22, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Preserve comments[].id from JSONL imports instead of relying on the database default.
  • Generate UUIDv7 IDs for imported legacy comments that omit id, matching the live comment path.
  • Add an init/export/reimport regression for stable comment ID round-trips.

Fixes #4050

Tests

  • go test ./internal/storage/issueops
  • go test -tags gms_pure_go ./cmd/bd -run 'TestEmbeddedInit/from_jsonl' -count=1\n\n_codex-gpt-5.5-medium on behalf of matt wilkie_

Preserve imported comment IDs when JSONL includes them and generate UUIDv7 IDs for legacy comments that omit id. Adds an init/export/reimport regression covering stable round-trips.

Refs gastownhall#4050

_codex-gpt-5.5-medium on behalf of matt wilkie_
@maphew maphew requested a review from a team as a code owner May 22, 2026 18:40
maphew added a commit to maphew/mybd that referenced this pull request May 22, 2026
Export current beads issue state after closing mybd-3y33 for gastownhall/beads#4103.

_codex-gpt-5.5-medium on behalf of matt wilkie_
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 7 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
internal/storage/issueops/create.go 0.00% 7 Missing ⚠️

📢 Thoughts on this report? Let us know!

@maphew maphew merged commit 5629e18 into gastownhall:main May 22, 2026
41 checks passed
@maphew maphew deleted the fix-jsonl-comment-ids branch May 22, 2026 18:53
popandpeek added a commit to popandpeek/beads that referenced this pull request May 31, 2026
* /internal/storage/schema/migrations: move non local after fk registration

* /internal/storage/schema/migrations: restore foreign key statements

* /internal/storage: get schema working, wip, deleting wisps from dependencies

* /internal/storage/embeddeddolt/create_issue_test.go: fix test

* /internal/storage: remove dual tx

* /internal/storage/schema: another fix

* /internal/storage/{embeddeddolt,schema}: fix test

* /internal/storage/embeddeddolt/version_control.go: fix one call site

* {CHANGELOG.md,internal}: cleanup and release notes

* docs: replace skill CLI reference copy with live pointers (#3953)

_codex-gpt-5- on behalf of maphew_

* docs: clarify config routing for tool settings (#3954)

_codex-gpt-5- on behalf of maphew_

* perf(search): tighten label and partial-id queries (#3967)

* perf(search): tighten label and partial-id queries

* Fix CI lint and label JSON parsing (bd-ycr)

* perf(deps): narrow recursive cycle checks (#3966)

* perf(deps): narrow recursive cycle checks

* fix: preserve mixed-table cycle detection

* perf(ready): page blocked checks for limited ready work (#3968)

* perf: page ready-work blocked checks

* fix: address ready-work review findings

* import: skip beads-jsonl metadata/header line on JSONL import

importFromLocalJSONLFull only special-cased _type=="memory"; the
canonical beads-jsonl header record ({"_schema":"beads-jsonl/1",
"_dolt_branch":...,"_sort":"stable-v1"}, no _type / no issue fields)
fell through to the issue path, unmarshalled into an empty Issue, and
aborted the entire import with "validation failed for issue : title
is required". Because maybeAutoImportJSONL runs this on every command
when the DB is empty, a single header line stranded every bd
invocation on an empty auto-imported database.

Skip records carrying the _schema sentinel (issues/memories never
do). Adds a regression subtest to TestImportFromLocalJSONL.

* test: keep update JSON stderr separate (#3992)

Capture stdout and stderr separately in the embedded update JSON-output test.
Validate only stdout as JSON so post-run stderr advisories do not contaminate the parsed output.

* test(embedded): split stdout/stderr in all JSON-parsing helpers (be-0ya2oe)

PR #3909 introduced an auto-export warning written to stderr. Tests that
called CombinedOutput() and then parsed the result as JSON would choke
on the warning text mixed into stdout.

Apply the pattern already established in list_embedded_test.go: for every
helper or inline call site that parses JSON or an ID from stdout, capture
stdout and stderr in separate bytes.Buffers and parse only stdout.

Covers 66 test files including duplicates, update, lint, todo, label,
count, swarm, close, list, status, types, find-duplicates, and more.

Closes TestEmbeddedDuplicates/dry_run and TestEmbeddedUpdate/update_json_output
failures from CI run 25967558419 and 25962887732.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(embedded): grep combined stdout+stderr for bd status text

The stdout/stderr split in 88e13542e applied uniformly to every helper
and inline call site, but bd commands write user-facing status messages
to stderr, not stdout:

 - cmd/bd/bootstrap.go:543: "Created fresh database" -> stderr
 - cmd/bd/bootstrap.go:610: "Imported %d issues" -> stderr
 - cmd/bd/import.go:270:     "Imported %d issues" -> stderr

After the split, bdImport and the bootstrap inline tests returned only
stdout, so `strings.Contains(out, "Imported 2 issues")` and friends saw
empty (or plan-only) output. CI shard 5 (TestEmbeddedBootstrap) and
shard 11 (TestEmbeddedImport) failed for this reason.

Fix: have bdImport return stdout+stderr concatenated (matches its
"Returns combined output" docstring) and have the two inline subtests
in TestEmbeddedBootstrap grep a combined buffer. JSON-parsing helpers
keep returning stdout-only as the original PR intended; this only
restores the prior CombinedOutput contract for non-JSON text greps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(ci): create flake lock PRs from fork

* Revert "fix(ci): create flake lock PRs from fork"

This reverts commit 26a7a96ca591522517a869192a6b2d3845094425.

* fix(ci): use app token for flake lock PRs

* flake.lock: Update

* test: share embedded command stream helper

Add a shared runCommandBuffers helper for embedded subprocess tests so the PR keeps stdout/stderr separation without repeating buffer setup in every JSON-output test.

_codex-gpt-5- on behalf of matt wilkie_

* fix(ready): apply filters to wisp ready work (#4008)

* fix(ready): apply filters to wisp ready work

* test(dep): expect current no-cycle message

* fix(ready): align wisp readiness filters

* /internal/storage/schema/schema.go: advisory lock for concurrent inits

* /internal/storage/schema/schema.go: panic on failure to unlock

* /internal/storage/schema/schema.go: use query context

* /internal/storage/schema/schema.go: fix panic

* refactor(ui): extract markdown rendering to internal/uimd package (#4009)

* refactor(ui): extract markdown rendering to internal/uimd package

Move RenderMarkdown out of internal/ui into a new internal/uimd package
so callers that don't need markdown don't pay the ~4.6 MB chroma/glamour
init cost on every bd subprocess.

Update callers: cmd/bd/show.go, show_display.go, show_children.go,
comments.go.

Closes be-ow4w54

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: release gate PASS for be-ow4w54 (uimd markdown extract)

* docs: narrow uimd release gate scope (bd-wisp-msz5y1)

---------

Co-authored-by: Jim Wordelman <jim@wordelman.name>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Julian Knutsen <julianknutsen@users.noreply.github.com>

* fix(schema): serialize Dolt schema migrations (#4005)

* fix(schema): serialize Dolt schema migrations

* fix(schema): harden migration lock handling (bd-wisp-5pl1m9)

* perf(deps): scan one cycle table for same-storage edges (#4004)

* perf(deps): narrow recursive cycle checks

* fix(deps): keep mixed-table cycle detection

* fix(deps): preserve mixed-table wisp cycle scans

* perf(ready): restrict blocked dependency scans to active IDs (#4002)

* perf(ready): restrict blocked dependency scan to active IDs

* test(ready): cover blocked dependency scan review fixes (bd-wisp-fmyceu)

* perf(get): query primary issues before wisp fallback (#4003)

* perf(get): query primary issues before wisp fallback

* fix(get): propagate wisp label lookup errors (bd-wisp-6fcee9)

* fix(dolt): adopt git origin on first push (#3940)

* fix(dolt): adopt git origin on first push

bd dolt push now wires an existing git origin when no Dolt remote is configured, while init without origin stays local and doctor shows the concrete adoption command. This keeps refs/dolt/data sync discoverable without treating plain remotes as existing Dolt history.\n\n_kilocode-openai/gpt-5.5- on behalf of maphew_

* fix(dolt): address origin adoption review nits

Share Dolt remote URL normalization between the push adoption path and doctor guidance, fail before mutating Dolt state when no beads workspace is selected, and cover CheckRemoteConsistency's adoption detail.

_codex-gpt-5-medium on behalf of maphew_

* perf(ready): narrow deferred-parent child filtering (#4001)

* perf(ready): narrow deferred parent filtering

* fix: address ready review findings (bd-wisp-r6076f)

* fix: materialize local table schemas for branch tests (#4014)

* fix: materialize local table schemas for branch tests

* fix: harden dependency target rename

* /{cmd,internal}: remove depends_on_id

* /{cmd,internal}: some fixes

* Add configurable JSONL import path

* /internal/storage/{dolt,issueops,schema}: remove comments

* /internal/storage/issueops/dependencies.go: fix formatting

* Regenerate CLI reference docs

* /internal/storage/{dolt,issueops}: more fixes

* /{cmd,internal}: get debug mode working

* /internal/storage/issueops/ready_work.go: fix ready work query

* /{cmd,internal}: some cleanup

* /internal/storage/issueops/ready_work_test.go: fix tests

* /{docs,website}: generate docs

* /internal/storage/issueops/dependencies.go: remove comment

* Document import path portability

* fix(audit): widen newID entropy to 8 bytes (be-yc9h)

The audit log's newID() drew only 4 random bytes (32 bits of entropy).
TestAppend_ConcurrentWritersUniqueIDs generates 2000 IDs per run, putting
the birthday-paradox collision rate at ~0.05% per run (n^2/2N for
n=2000, N=2^32). That rate matches the failure reproduced locally
(4 collisions in 3000 runs, ~0.13%) and the CI failure observed on
PR #3913 (duplicate ID int-3f25833f).

The earlier atomic-write fix (#3301) addressed a different failure
mode (torn lines under concurrent O_APPEND) and is unrelated — the
new failure is entirely in-memory: returnedIDs map collision at
concurrency_test.go:104, before any file scan.

8 bytes (64-bit) drops the collision probability for this workload to
~1e-13, effectively zero. No callers depend on the ID length or format
beyond the "int-" prefix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* /{cmd,internal}: wip, implementing init with proxied-server

* /{cmd,internal}: refactoring

* /{cmd,internal}: cleanup

* /{cmd,internal}: more cleanup

* /.github/workflows/ci.yml: run tests in CI

* test: add perf reference benchmarks (#4032)

* test: add perf reference benchmarks

* test: tighten perf reference benchmarks

* Fix perf benchmark review findings (bd-fsgqsj)

* /internal/storage/{uow,domain}: wip stubs

* /internal/storage/domain/db: issue

* /internal/storage/domain: add event

* /internal/storage/domain/db: comments

* /internal/storage/domain/db: dependency

* /internal/storage/domain/db: handle wisps too

* /internal/storage/domain: consolidate

* /internal/storage: remove some comments

* /internal/storage/{domain,uow}: dependency

* /internal/storage: cleanup

* docs: clarify export versus database backup (#4048)

* docs: clarify export versus database backup
* docs: remove remaining JSONL backup wording

* test: prevent Cobra command flag cache races (#4011)

* test: prevent Cobra command flag cache races

* chore: release gate PASS for be-psutr9 (cobra/pflag race-fix)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs(gate): update be-psutr9 gate — re-review PASS (be-mouz), fix stale file list

Re-review be-mouz confirmed PASS on current HEAD 1b5536624. Correct files
changed: stdio_race_guard_test.go + test_helpers_pure_test.go only (earlier
version listed notion_test.go/stale_test.go which are no longer in the diff).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address PR review findings (bd-wrntbm)

---------

Co-authored-by: Julian Knutsen <julianknutsen@users.noreply.github.com>
Co-authored-by: Jim Wordelman <jim@wordelman.name>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(schema): unify migration advisory lock path (#4031)

* fix(storage): preserve mixed issue and wisp reads (#3991)

* fix(schema): avoid sweeping dirty data during migrations

* fix: guard schema migration dirty commits (bd-wisp-g6c0g)

* fix(storage): preserve mixed issue and wisp reads

* fix(schema): unstage old tables before migrations

* test(dolt): add generated column cascade repro

* Repair PR 3991 rebase against current storage schema

* feat(storage): honor .beads/config.yaml types.custom in server-side validation (#4024) (#4026)

* feat(storage): honor .beads/config.yaml types.custom in server-side validation (#4024)

ResolveCustomTypesInTx previously consulted config.GetCustomTypesFromYAML
only in a narrow error-fallback path: when both the custom_types table AND
the types.custom config-string read had failed. As a result, a project that
declared types.custom in .beads/config.yaml (the version-controlled config
shape that downstream wrappers like gastownhall/gascity expect) could not
create or update beads of those types — server-side schema validation
rejected them because it never saw the YAML entries.

Overlay model (per #4024's proposed behavior):
  - Built-in types stay as the enforced floor (handled by IsValidWithCustom).
  - Database custom_types are the next layer (operator-/agent-installed types).
  - .beads/config.yaml types.custom is the topmost union-add (project-extension
    types declared in version-controlled config).
  - A bead whose type is in none of the three still fails validation cleanly.

Extracts mergeWithYAMLCustomTypes(dbTypes, yamlGetter) as a pure helper so
the union logic is unit-testable without sql.Tx mocking. The new helper:
  - Preserves order: DB types first, YAML-only types appended in declared order.
  - Dedupes whitespace-trimmed entries.
  - Drops empty/whitespace-only entries.
  - Tolerates nil getter and nil dbTypes.

Tests in internal/storage/issueops/config_helpers_test.go:
  - TestMergeWithYAMLCustomTypes — 7 table cases: both empty, yaml-only,
    db-only, db+disjoint-yaml union, db+overlapping-yaml dedup, yaml
    internal-dup dedup, whitespace dropping.
  - TestMergeWithYAMLCustomTypes_NilGetter — explicit nil-getter guard.

The pre-existing ResolveCustomTypesInTx error-fallback path is preserved:
when the config-string read errors AND YAML has types, return the YAML
overlay anyway with the original error — so a stable-bd project running
pre-#3691 doesn't lose its types.custom validation just because the
config-string read errored.

Downstream context: this unblocks gastownhall/gascity#2154 (the Gas City
side, which writes types.custom into .beads/config.yaml via
gc-beads-bd.sh's ensure_types_custom_in_yaml). Once this lands and bd
deployments adopt the fixed build, Gas City projects no longer need the
fallback workaround of mirroring types.custom into the database side.

* fix: address PR #4026 review findings (#4024)

Two findings from Copilot review:

1. ResolveCustomTypesInTx returned the YAML overlay alongside a non-nil
   error when the in-tx config-string read failed. In-tx callers
   (NewBatchContext, UpdateIssueInTx) treat any error as fatal, so the
   YAML-fallback path was effectively unreachable on pre-migration /
   degraded DBs — defeating the #4024 overlay goal precisely in the
   scenario the overlay was meant to cover. Mirror
   ResolveCustomStatusesDetailedInTx: when YAML supplies types, return
   them with a nil error; otherwise propagate the DB error. Extracted
   into customTypesYAMLFallback for unit testing without sql.Tx mocking.

2. getConfigList (the helper backing GetCustomTypesFromYAML /
   GetInfraTypesFromYAML / GetCustomStatusesFromYAML) used
   viper.GetString + comma-split, which silently ignored the YAML
   sequence form named in #4024 (`types: { custom: [step, wisp] }`).
   List-form .beads/config.yaml declarations therefore yielded an empty
   overlay. Try viper.GetStringSlice first (supports both block-list
   and inline-flow syntaxes), keeping the comma-split fallback for the
   legacy `types.custom = "step,wisp"` string form.

Tests: TestCustomTypesYAMLFallback covers the fallback decision;
TestGetCustomTypesFromYAML_ListForm and TestGetCustomTypesFromYAML_InlineListForm
pin both YAML sequence syntaxes.

Refs: gastownhall/beads#4024

---------

Co-authored-by: sjarmak <stephanie.jarmak1@gmail.com>

* ci(nix): auto-update default.nix vendorHash on dependabot Go bumps (#3804)

* chore: track workflow formulas for portability

Force-add gh-issue-to-pr and gh-pr-review formulas so they can be
cloned to new machines. These live in .beads/formulas/ which is
normally gitignored, but formulas are code (not database state).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: untrack workflow formulas

Remove gh-issue-to-pr and gh-pr-review formulas from git tracking.
These are already covered by .gitignore (.beads/) and shouldn't be
sent upstream.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* ci(nix): auto-update vendorHash for dependabot Go bumps (beads-bz5)

Dependabot Go-module bumps consistently fail the `nix build .#default`
CI gate because default.nix has a hardcoded vendorHash that any
go.mod/go.sum change invalidates. PRs sit stuck for days (#3468, #3637,
#3638 currently affected) until a maintainer manually computes the new
hash. This workflow detects the drift, runs the existing
scripts/update-nix-vendorhash.sh, and pushes the hash bump back to the
dependabot branch.

Uses pull_request_target because GITHUB_TOKEN is read-only on
pull_request workflows triggered by dependabot. Safety guards: only
runs when actor and PR user are both dependabot[bot] AND the PR head
is on the upstream repo (dependabot never opens PRs from forks).

Caveat documented in CONTRIBUTING.md: GitHub does not retrigger
pull_request workflows on GITHUB_TOKEN-authored commits, so a
maintainer may need to re-run nix-build once after the auto-fix push.
The workflow's own success serves as the green signal that the hash
is correct.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(init): mark server-mode Dolt dirs compatible

* fix(init): surface marker write failures

* fix(init): make JSONL auto-export opt-in (#4063)

* fix(init): make JSONL auto-export opt-in

Default export.auto and export.git-add to false for new repos, make the init prompt opt-in, and update docs/tests to describe JSONL as an optional viewer/interchange/backup export rather than core sync behavior.

Fixes GH#4062

* refactor(auto-export): check commit before throttle

Move cheap Dolt commit change detection ahead of the auto-export throttle and extract the throttle decision into a tested helper. This keeps the default-off design while carrying the compatible part of Jeremy Longshore's GH#4061 refactor.

Based-on: 313e1f82588ed02552538ac530424ff34bf584b5

Co-authored-by: jeremylongshore <jeremylongshore@users.noreply.github.com>

* docs: regenerate versioned CLI references
* docs: call out JSONL export opt-in change

---------

_codex-gpt-5.5-medium on behalf of CI Bot_
_codex-gpt-5.5-high on behalf of matt wilkie_
Co-authored-by: CI Bot <ci@beads.test>

* fix(init): tighten Dolt marker handling (#4083)

* fix(export): guard empty auto-export over populated JSONL

Skip auto-export before writing when the current export would contain no issue records but the target JSONL already contains issues. This prevents an opt-in auto-export from deleting the only visible issue export in empty-DB migration/recovery scenarios.

Add JSONL issue counting coverage and a subprocess regression that preserves a populated export target when a memory-only write triggers auto-export.

Fixes GH#4033.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* test(export): assert byte-stable JSONL across all record types (#4086)

TestExportMemoryDeterminism (GH#3474) proves only memory lines are
stable. This generalizes the invariant from gh beads 3787: running
`bd export` repeatedly over unchanged data must produce byte-identical
output for every record type, not just memories.

The new test seeds issues at the same priority and creation time (so the
export ORDER BY must fall through to the id tiebreaker — the total-order
property the determinism guarantee depends on), each with multiple
labels, parent-child dependencies, and comments, plus several memories
keyed out of sorted order. It exports five times and asserts every run
is byte-identical, then sanity-checks that each record type was actually
present so a future change can't make the stability assertion pass
vacuously.

Catches any future collection serialized without a stable total order
(Go's randomized map iteration being the usual culprit) before it ships,
rather than spot-fixing per record type.

_claude-opus-4-7-medium on behalf of matt wilkie_

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Guard auto-export against JSONL-only records (#4090)

_codex-gpt-5.5-medium on behalf of maphew_

* Make auto-export git add lock-aware (#4089)

_codex-gpt-5.5-medium on behalf of maphew_

* fix: guard JSONL auto import/export edge cases

Protect JSONL auto-import/export paths around server mode, pre-commit staging scope, and intentional prune/purge removals.\n\n_codex-gpt-5.5-medium on behalf of matt wilkie_

* Fix ephemeral graph apply readiness and dependency cost (#3943)

* fix: speed up ephemeral graph apply

* fix: harden graph apply dependency validation

* fix: address graph apply review findings (bd-hej2e4)

* Stamp auto-import attempts for unchanged JSONL

* fix(auto-import): pass serverMode=false in rebased stamp tests

main's maybeAutoImportJSONL gained a 4th serverMode bool param after this
branch's base commit. Rebasing the stamp commit onto current main keeps the
4-arg signature, but the cherry-picked stamp tests still called it with 3
args, breaking the CGO_ENABLED=0 test-binary compile. These tests exercise
the import/stamp path, so they pass serverMode=false (matching the existing
import-path tests). serverMode=true is already covered separately.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(export): guard auto-export against richer JSONL overwrite

Refuse opt-in auto-export when the existing target contains records outside auto-export scope, preserving richer JSONL files instead of silently replacing them with the viewer-scoped auto-export subset.

Fixes #4069

Validation:
- go test -tags gms_pure_go ./cmd/bd -run 'TestGuardAutoExportOverwrite|TestShouldExport|TestShouldRunPostCommandAutoExport|TestShouldSkipEmptyAutoExport|TestMissingJSONLIssueIDsInStore' -count=1
- go test -tags gms_pure_go ./cmd/bd (fails on existing Dolt server/test-environment setup issues)

_codex-gpt-5.5-medium on behalf of maphew_

* fix(export): fail on auto-export git add errors

Surface opt-in auto-export git-add failures through the command error path and leave export state unsaved so the next run can retry staging.\n\nFixes #3970.\n\n_codex-gpt-5.5-medium on behalf of matt wilkie_

* Consider entire PR thread, clarifying info are often in comments (#4105)

agents are often only reading the opening post and the code, missing the follow-on comments and review recommendations. this is an attempt to fix that.

* fix(import): preserve JSONL comment IDs (#4103)

Preserve imported comment IDs when JSONL includes them and generate UUIDv7 IDs for legacy comments that omit id. Adds an init/export/reimport regression covering stable round-trips.

Refs gastownhall/beads#4050

_codex-gpt-5.5-medium on behalf of matt wilkie_

* /{cmd,internal}: read optimizations

* /{cmd,internal}: cleanup

* fix(create): set status=deferred when --defer date is in the future (GH#4071)

bd create --defer correctly sets defer_until but leaves status as "open",
unlike bd update --defer and bd defer which both set status=deferred
(fixed in GH#3233 for update only). This causes bd list --status deferred
to miss the issue and bd undefer to reject it.

Mirror the update.go logic: if defer_until is in the future, set
status=deferred; if in the past, keep open (the existing warning
already covers this case).

* fix(close): treat re-close of already-closed issue as idempotent success (GH#4025)

When bd close is called on an issue already in closed state within the
same wall-clock second, the UPDATE touches no columns and RowsAffected
returns 0. Previously this was misinterpreted as "issue not found".

Now when RowsAffected==0, query the row's current status: if already
closed, return success silently (no duplicate event recorded). If the
row truly doesn't exist, preserve the original "issue not found" error.

Follows the same idempotent pattern used in claim.go.

* test(cmd/bd): route sync.Once test temp dirs through TestMain cleanup (#4108)

Five package-level sync.Once builders in cmd/bd/ called
os.MkdirTemp("", ...) with no corresponding cleanup, leaking
~179MB-1.4GB of test artifacts per run into $TMPDIR. On tmpfs
hosts (e.g. Fedora Atomic /tmp = 50% of RAM) this exhausted /tmp
after a few runs. Leaking sites:

  - test_repo_beads_guard_test.go   beads-bd-tests-*
  - test_helpers_pure_test.go       bd-init-test-*
  - init_embedded_test.go           bd-embedded-init-test-*
  - shared_server_integration_test  beads-shared-server-bd-*
  - init_permissions_test.go        bd-init-permissions-test-*
  - explicit_db_nodb_test.go        bd-testbin-*

Fix: anchor all six temp dirs under the existing TestMain tmp
parent via a new testTempDir helper. The defer in testMainInner
already calls forceRemoveAll on that parent (chmod-tolerant for
Go modcache 0444 files), so all six get cleaned in one place.

Also adds 'make clean-test-tmp' (scripts/clean-test-tmp.sh) for
sweeping orphans left by SIGKILLed test runs, and documents the
tmpfs caveat in AGENT_INSTRUCTIONS.md.

Fixes gastownhall/beads#4106 (bd-3q2u)

_amp-unknown-model-unknown-reasoning on behalf of matt wilkie_

Amp-Thread-ID: https://ampcode.com/threads/T-019e512d-edf1-72e3-a252-6328e63bff3f

Co-authored-by: Amp <amp@ampcode.com>

* feat(show): count-only JSON details with opt-in streamed payloads

Summary:
- Make default `bd show --json` emit relationship/comment counts without materializing dependents and comments.
- Add `--include-dependents` and `--include-comments` for callers that need full streamed payloads.
- Add Count* storage APIs and iterator APIs across Dolt, embedded Dolt, telemetry, and test stubs.
- Keep count and dependent-iterator semantics aligned with existing relationship APIs by including `wisp_dependencies`.

Verification:
- Full PR CI green at 3e036880f.
- go test -tags gms_pure_go ./cmd/bd -run 'TestShallowDependentsForJSON|TestIssueDetailsCountOnlyJSON' -count=1

Refs #4010.
Addresses be-ijck6q.

_codex-gpt-5.5-medium on behalf of matt wilkie_

Co-authored-by: Jim Wordelman <jim@wordelman.name>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(changelog): credit Kevin Glynn for JSONL cleanup origin

Add the missing attribution for Kevin Glynn surfacing the JSONL source-of-truth mismatch that led to GH#4062 and the follow-up JSONL export/import cleanup.

Refs GH#4062

Reported-by: Kevin Glynn <kglynn@pryoninc.com>

_codex-gpt-5.5-high on behalf of matt wilkie_

* fix(schema): reorder 0041 FK before generated column for Dolt 2.0.6 (#4120)

Dolt 2.0.6 rejects ADD FOREIGN KEY on the base column of a stored
generated column (errno 1105: "Cannot add foreign key on the base
column of a stored generated column"). Migration 0041 created the
generated depends_on_id column (COALESCE over the three split target
columns) and THEN added fk_dep_issue_target on depends_on_issue_id — a
base column of that generated column — so fresh-DB migration aborts
mid-chain on Dolt 2.0.6.

CI installs `latest` dolt; once 2.0.6 shipped (mid-day 2026-05-22 UTC),
every PR's "storage domain + uow", ubuntu/macos test, and repro-dolt
jobs went red. The parent main commit passed on dolt 2.0.5; the next
runs failed on 2.0.6. Not caused by any application change.

Reordering the FK add to BEFORE the generated-column add yields an
IDENTICAL final schema (verified via SHOW CREATE TABLE) and works on
both 2.0.5 and 2.0.6.

Verified under dolt 2.0.6 (failed before, pass after):
- internal/storage/uow TestNewDoltServerUOWProvider_HappyPath
- internal/storage/uow TestNewDoltServerUOWProvider_ConcurrentInstantiation
- scripts/repro-dolt-prod-timeouts TestSeedProductionShapeFullSmallIssueCountRealSchema
No regression on dolt 2.0.5.

Co-authored-by: Jim Wordelman <jim@wordelman.name>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* ci: gate regression tests on risky PR paths (#4123)

Run the differential regression workflow for PRs, but keep the expensive job behind a detector that only enables it for CLI/storage/type/regression/build-input changes or an explicit run-regression label. Pushes to main and manual dispatches continue to run the suite unconditionally.

Refs bd-yd7.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* feat(init): auto-configure contributor routing on fork detect (be-7daa14) (#4028)

* feat(init): auto-configure contributor routing on fork detect (be-7daa14)

Adds autoConfigureForkContributor to init_contributor.go and wires it
into bd init after the fork-exclude block. Three output variants:

  happy path  — ▶ Fork detected block with ✓ lines and opt-out hint
  opt-out     — ⚠ message when --role=maintainer is set on a fork
  re-init     — ⚠ message when routing.contributor already configured

CI/quiet mode produces no output. Non-interactive, idempotent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(init): suppress autoConfigureForkContributor output in non-interactive mode

Pass quiet || nonInteractive to autoConfigureForkContributor so CI and
scripted bd init --non-interactive runs do not emit fork-detection output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: release gate PASS for be-7daa14 (feat(init): auto-configure contributor routing)

* test(init): cover fork auto contributor routing

Add an embedded init regression test for PR 4028's fork auto-routing path and update the release-gate evidence after rebasing onto current main.

PR: https://github.com/gastownhall/beads/pull/4028

_codex-gpt-5.5-high on behalf of matt wilkie_

* fix(init): run fork auto routing before store close

Keep the PR 4028 fork contributor setup on an open store so routing config writes persist and are included in the initial commit.

_codex-gpt-5.5-high on behalf of matt wilkie_

* docs(gate): describe PR 4028 maintainer merge

Update the release gate to reflect that PR 4028 is maintained on the original contributor branch instead of through a replacement PR history.

_codex-gpt-5.5-high on behalf of matt wilkie_

---------

Co-authored-by: Jim Wordelman <jim@wordelman.name>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: matt wilkie <maphew@gmail.com>

* fix: repair PR4107 blocked-state corruption (#4139)

* test: reproduce pr4107 corruption regressions (bd-rzn)

* test: expand pr4107 corruption coverage (bd-rzn)

* fix: repair pr4107 blocked-state corruption

* fix: restore green Dolt storage suite

* test: repair regression workflow after show json streaming

* fix: make mixed blocked migration upgrade-safe

* test(ready): benchmark limited wisp-heavy ready queries

* perf(ready): page limited ephemeral scans

* Fix review blockers for PR4107 corruption repair (bd-yueuq)

* fix(create): commit labels during initial issue creation (#4149)

* fix(dolt): commit initial create relations

* fix(cli): create labels in initial issue write

* fix: address create review findings

* fix: preserve tolerant import dependency handling

* fix: skip mixed-bucket import dependencies (bd-6r6rc)

* feat(schema): schema-skew guard — hard fail on forward DB drift (be-wwbsv)

Add SchemaSkewError type, checkSchemaSkew, CheckForwardDrift,
--ignore-schema-skew flag, and dedicated error handler in main.go.

- internal/storage/schema/schema.go: SchemaSkewError{DBVersion,BinaryVersion}
  with Error()/UserMessage()/EscapeHint(); IsSchemaSkewError helper;
  checkSchemaSkew (unexported, BD_IGNORE_SCHEMA_SKEW=1 escape hatch);
  CheckForwardDrift exported for read-only store path.
- internal/storage/dolt/store.go: ReadOnly branch calls CheckForwardDrift
  before the existing initSchema else-branch; returns *SchemaSkewError on
  forward drift.
- cmd/bd/schema_skew.go: handleSchemaSkewJSON writes structured JSON to stderr
  with schema_skew subobject (current_version, required_version, delta).
- cmd/bd/main.go: --ignore-schema-skew persistent flag (sets
  BD_IGNORE_SCHEMA_SKEW=1); SchemaSkewError errors.As check before
  FatalError("failed to open database") gives dedicated actionable UX.

Tests: schema_skew_test.go files from be-cdhvj (unit + dolt integration +
cmd/bd JSON shape + flag registration) — all 14 pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(schema): qualify go install guidance with gms_pure_go

The schema-skew error message pointed users at a bare
'go install github.com/steveyegge/beads/cmd/bd@latest', which the
build-tag policy guard (scripts/check-go-install-guidance.sh) rejects
because it takes the CGO+ICU path on many hosts. Match the rebuild
line directly above it: CGO_ENABLED=0 ... -tags gms_pure_go. Updated
the exact-copy test mirror in lockstep.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(cli): regenerate CLI reference for --ignore-schema-skew

The PR added the global --ignore-schema-skew flag but did not
regenerate docs, failing the 'doc flags freshness' check. Output of
./scripts/generate-cli-docs.sh ./bd — adds the flag to the global
flags block in docs/CLI_REFERENCE.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* test(cmd/bd): be-0x25q flag-to-env-var propagation gate for --ignore-schema-skew

Add TestIgnoreSchemaSkewFlagPropagatesEnvVar: verifies that PersistentPreRun
sets BD_IGNORE_SCHEMA_SKEW=1 when --ignore-schema-skew is true, so all
store-open paths see the escape hatch uniformly via the env-var route.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(audit): widen newID entropy to 16 bytes + bump test to 8000 IDs (be-5uxe)

4 bytes (32-bit) collided at birthday rate; 8 bytes was an intermediate step.
16 bytes (128-bit) makes collision probability ~9e-32 at 8000 IDs. Bump
entriesPerWriter from 250 to 1000 (8000 total) to stress-test without flaking.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(list): be-a5z add --skip-labels hydration toggle (AD-02)

Add bd list --skip-labels to suppress the labels JOIN entirely (distinct
from --no-labels filter). Plumbs through IssueFilter.SkipLabels so the
issueops search path skips GetLabelsForIssuesInTx; cmd-level post-query
re-hydration is also gated. Conflict validation rejects combination with
any label-filter flag (Wireframe 5, exit 2). With the flag set, JSON
output wraps as {issues, meta} with meta.skip_labels=true and every
issue's labels field is explicit []. Default JSON shape (top-level
array) is byte-for-byte unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: release gate PASS for be-l9q (bd list --skip-labels AD-02)

* docs(list): regenerate CLI reference for --skip-labels (be-rqm7yb)

CI's "Check doc flags freshness" gate was failing because the new
--skip-labels flag added in this PR wasn't reflected in the generated
CLI reference. Regenerated via ./scripts/generate-cli-docs.sh.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* /{cmd,internal}: add migrate schema command

* /{cmd,docs,website}: gen docs

* /internal/storage/dolt: fix after rebase

* /{cmd,docs,website}: gen docs again

* init: run Codex project setup by default

Run Codex project setup during `bd init` using the existing setup installer, and stage generated Codex project files in the bootstrap commit.

Also refresh the generated setup/init docs for the new default behavior.

Co-authored-by: Eugene Brevdo <ebrevdo@gmail.com>

_codex-gpt-5.5-medium on behalf of CI Bot_

* fix(migration): run schema migrations on a connection with no read timeout

Schema migrations can run far longer than the 10s ReadTimeout baked into the
main connection pool (buildServerDSN) -- e.g. migration 0047's full-table
is_blocked recompute, which takes ~60s on a few-thousand-row database. Running
migrations over that pool aborts mid-flight with "i/o timeout" and leaves
tables dirty, which then trips the pre-existing-dirty-tables guard on every
subsequent attempt, permanently wedging the upgrade.

initSchema now runs the migration pass over a dedicated single connection with
ReadTimeout/WriteTimeout=0. Cancellation is governed by the caller's context
rather than a fixed deadline, so a long migration completes instead of being
severed partway through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* auto-import: gate server mode at call site (#4170)

* auto-import: gate server mode at call site

Keep JSONL upgrade recovery as embedded-only behavior while moving the server-mode policy out of maybeAutoImportJSONL and into the caller predicate. This preserves the server-mode skip introduced by #4091 while addressing the call-shape concern raised in #3995.

Credit: Shaun Cutts raised the poor calling shape in gastownhall/beads#3995.

_codex-gpt-5.5-medium on behalf of CI Bot_

* auto-import test: rename ServerModeFallback → FallbackImporter

The fallback importer path is mode-agnostic now that maybeAutoImportJSONL
no longer takes serverMode. Rename three test functions and refresh the
fakeFallbackStore doc-comment so the names match what they actually test.

No behavior change.

_claude-opus-4-7-medium on behalf of CI Bot_

---------

Co-authored-by: CI Bot <ci@beads.test>

* docs: audit CI test surface (bd-jot)

* /internal/storage/domain: more impls

* /{cmd,internal}: wip create

* /cmd/bd: wip need tests

* fix(list): honor skip-labels in JSON output

Suppress label aggregation in counted list queries when SkipLabels is set, and route bd list --skip-labels --json through the promised issues/meta response with explicit empty labels arrays.

Adds unit coverage for the response wrapper and embedded CLI coverage for a labeled issue.

_codex-gpt-5.5-medium on behalf of CI Bot_

* docs: guard plugin CLI reference pointer (#4177)

Record the plugin CLI reference as a pointer-only resource and extend the doc freshness check so it fails if that resource drifts back into a copied command reference.

_codex-gpt-5.5-medium on behalf of CI Bot_
Co-authored-by: CI Bot <ci@beads.test>

* gc: recheck closed deletion candidates (#4171)

* gc: recheck closed deletion candidates

Add an application-level safety recheck before destructive closed issue deletion. Candidates must still be closed, have a closed_at timestamp, pass the cutoff in Go, and not be pinned before gc/prune/purge deletes them.

_codex-gpt-5.5-medium on behalf of CI Bot_

* gc: count nil deletion candidates separately

A nil entry from storage is a different (worse) condition than a row
with no closed_at; reporting them both under missing_closed_at hides
the distinction in the warning that diagnoses why a delete was skipped.

_claude-unknown-model-medium on behalf of CI Bot_

---------

Co-authored-by: CI Bot <ci@beads.test>

* docs: design required CI gate topology (#4179)

_codex-gpt-5.5-high on behalf of CI Bot_

* docs: check reference doc freshness (#4181)

_codex-gpt-5.5-medium on behalf of matt wilkie_

* docs: gate llms-full freshness with CLI reference (#4182)

_codex-gpt-5.5-medium on behalf of CI Bot_

Co-authored-by: CI Bot <ci@beads.test>

* /{cmd,internal}: create tests

* test: split tracker pure-go helpers (#4183)

_codex-gpt-5.5-medium on behalf of CI Bot_

* /{.github,cmd}: remove parrallelism for ps tests

* /cmd/bd: cleaning up

* perf(search): Q2+Q3+Q5 — skip-wisps merge, id-shrunk Pattern B, wisp count cache (bd-qi4qr, bd-co6m6)

Q2 (bd-qi4qr): add IssueFilter.SkipWisps; set it in count/query/list callers that never
need ephemeral results; SearchIssuesInTx returns early before the wisps merge.

Q3 (bd-co6m6): add IssueFilter.NoIDShrink; searchTableInTx dispatches Pattern B
(SELECT id LIMIT n + batch hydrate) when Limit>0 and NoIDShrink is false; Pattern A
(full 47-column scan) retained for unlimited queries and NoIDShrink callers.

Q5 (bd-qi4qr): per-process wisp row-count cache on DoltStore (wispCountCached/
wispCountZero); SearchIssues short-circuits SkipWisps when table is known empty,
avoiding the COUNT(*) hit on every search; cache invalidated on CreateIssue,
CreateIssuesWithFullOptions, DeleteIssue, DeleteIssues, and DemoteToWisp.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* perf(count): Q1 — SQL COUNT(*) aggregate replaces SearchIssues+len in bd count (bd-xoi0x)

- Add CountIssues/CountIssuesByGroup to Storage interface and implement in
  DoltStore, EmbeddedDoltStore, and InstrumentedStorage (telemetry pass-through)
- New issueops.CountIssuesInTx / CountIssuesByGroupInTx: SELECT COUNT(*) with
  the same filter and wisps-merge semantics as SearchIssuesInTx
- Group-by-label uses subquery (avoids Dolt joinIter panic); other group-bys
  use GROUP BY on the main column; assignee/priority keys normalized to match
  existing display format ("(unassigned)", "P0"/"P1")
- cmd/bd/count.go: routes through CountIssues (no groupBy) or CountIssuesByGroup
  (--by-* flags); total for JSON/human output computed from group sums

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(spike): joinIter panic repro test for current go-mysql-server (bd-wsgws)

TestJoinIterPanic_Spike covers 5 JOIN/EXISTS shapes on the pinned go-mysql-server:
- inner JOIN issues+dependencies (GetMoleculeProgress workaround)
- correlated EXISTS subquery (ComputeBlockedIDsInTx workaround)
- double correlated EXISTS (blocked+active check)
- non-correlated IN subquery (baseline — known safe, already in prod)
- mergeJoinIter type+status+priority combination (ready_work.go workaround)

Each sub-test uses panic recovery: t.Error if panic still fires (bug present),
t.Skip if no panic (workaround liftable). Requires a live Dolt server to run.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(schema): migrate TEXT → LONGTEXT for large-content columns (be-knye)

issues/wisps description, design, acceptance_criteria, notes, close_reason
and comments.text were TEXT (65535 byte limit). Bead descriptions with
embedded base64 images exceeded this, causing MySQL Error 1105 on bd import.

Migration 0043 changes these to LONGTEXT; each MODIFY is guarded by an
INFORMATION_SCHEMA check for idempotency.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(schema): preserve wisps DEFAULT '' and renumber LONGTEXT migration → 0046 (be-knye)

The LONGTEXT migration had two defects: it broke CI on a fresh schema and
would have been a silent no-op on every existing database.

1. DEFAULT '' dropped. MODIFY COLUMN ... LONGTEXT NOT NULL replaces the
   whole column definition, so it dropped the DEFAULT '' that the wisps
   description/design/acceptance_criteria/notes columns were created with
   (migration 0020). Inserts that omit those columns (e.g. the domain
   suite's seedWispRow, which inserts id+title only) then failed with
   "Field 'description' doesn't have a default value" — the CI failure in
   "Test (storage domain + uow)". Restated DEFAULT '' on those four wisps
   columns and preserved the nullable DEFAULT '' on close_reason for both
   issues and wisps. issues description/design/acceptance_criteria/notes
   and comments.text were created NOT NULL with no default (0001/0004) and
   intentionally keep none.

2. Duplicate migration number. The migration was numbered 0043, colliding
   with 0043_drop_dependencies_generated_column and sitting at/below the
   latest applied version (main is at 0045). The runner applies a migration
   only when its version > MAX(schema_migrations.version), so any database
   already at v43+ would skip the LONGTEXT widening forever. Renumbered to
   0046.

Verified end-to-end against a live dolt sql-server through schema.MigrateUp:
all six target columns become longtext, wisps defaults are preserved, a
minimal wisp insert succeeds, a minimal issue insert is still correctly
rejected, and a second MigrateUp is a clean no-op.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(schema): widen events/wisp_events value columns to LONGTEXT (be-kkp)

bd update was failing for beads with large descriptions/notes because
RecordFullEventInTable marshals the full oldIssue + updates state to JSON
and writes it into events.{old,new}_value and wisp_events.{old,new}_value,
both originally TEXT (max 65535 bytes). A ~6KB markdown description plus
other fields routinely overflowed the cap, dropping audit trail events.

Migration 0044 widens events.{old,new}_value to LONGTEXT. Ignored migration
0005 widens wisp_events.{old,new}_value (applied after the regular migrations
that create the dolt-ignored wisp_events table).

Rebased from be-kkp (originally migration 0034; renumbered to 0044 to avoid
conflict with main's existing 0034_add_spec_id_column migration).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* /{cmd,internal}: more fixes

* /internal/storage/domain: address feedback

* docs: default PR fixes to contributor branches (#4184)

Make in-place maintainer edits on contributor PR branches the default path and document replacement PRs as the exception.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* /{cmd,internal}: remove comments

* /{.github,cmd}: comment out ci, disable proxied-server mode

* feat(config): add dolt.mode config key with ambiguous-config warning

Add dolt.mode as a config.yaml key ("server" or "embedded") so users
can set their preferred Dolt connection mode in config.yaml instead of
relying solely on env vars or --server flag.

- Add dolt.mode to YamlOnlyKeys with validation
- IsDoltServerMode() falls back to config.yaml dolt.mode
- bd init reads dolt.mode from config.yaml when --server not passed
- Warn when dolt.host points to a remote but server mode is off
- No behavior change for users with no config

* fix(init): platform-accurate config path in warning and add init test coverage

Address review feedback on PR #3533:

1. Replace hardcoded ~/.config/bd/config.yaml in the ambiguous-host
   warning with config.UserConfigYamlPath(), which returns the
   platform-appropriate path (respects macOS ~/Library/Application Support
   when ~/.config/bd/ doesn't exist).

2. Use isLocalHost() instead of inline != "127.0.0.1" checks in the
   ambiguous-host detection, properly handling localhost, ::1, and 0.0.0.0.

3. Fix IsDoltServerMode() to respect metadata.json embedded mode over
   config.yaml server mode — project-local metadata takes priority over
   user-global config.

4. Add cmd/bd init subprocess tests covering:
   - ambiguous_host_warning: remote host without server mode emits warning
   - ambiguous_host_quiet_suppresses_warning: --quiet suppresses it
   - ambiguous_host_local_no_warning: localhost doesn't trigger warning
   - config_yaml_dolt_mode_server_metadata: dolt.mode: server in
     config.yaml triggers server mode during init

5. Add TestIsDoltServerMode_MetadataEmbeddedNotOverriddenByConfigYaml
   to verify the priority invariant.

* fix(init): hard-fail when dolt.host/dolt.port set without server mode

Per reviewer feedback: embedded mode has no host/port, so configuring
dolt.host or dolt.port without enabling server mode is always a
misconfiguration. Replace the soft warning with FatalError and extend
the check to cover dolt.port as well.

* test(config): dolt.mode validation rejects invalid values

* feat(config): validate dolt.mode accepts server or embedded

* test(init): port-only config should not fatal

* fix(init): narrow hard-fail to remote host, not port-only

* test(init): host-only fatal guard + remove DRAFT_PR.md

* fix(init): apply config server mode before validation

Resolve remaining maintainer edge cases on PR #3533: apply config.yaml dolt.mode before server/embedded validation and honor env host precedence in the remote-host guard.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* fix setup injection for symlinked agent files

Avoid following symlinked agent instruction files when installing managed setup sections. This preserves symlink mode and target content while still allowing normal files to receive integration text.\n\nRefs gastownhall/beads#3722\n\n_codex-gpt-5.5-medium on behalf of matt wilkie_

* fix(dep): keep relates-to out of dependency tree

Loose relates-to edges are graph links, not dependency-tree edges. Skipping them during tree traversal prevents bidirectional relates-to links from hiding nodes through cycle detection while preserving dep list output.

Fixes GH#3936.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* fix(list): keep relates-to out of `bd list` tree mode (#3936)

The original PR (gastownhall/beads#4189) only filtered relates-to from
`bd dep tree`. The user-facing bug in #3936 is in `bd list`, which builds
its own tree via `buildIssueTreeWithDeps` and was unaffected by that
change.

`buildIssueTreeWithDeps` treats any dependency whose target is an epic as
a parent-child edge. For a relates-to between two epics, that:

1. Nests one epic under the other in tree mode (one-direction case).
2. Marks both endpoints as children of each other when bidirectional,
   so neither is emitted as a root — the entire subtree silently drops
   from output while the footer count stays correct.

Skip relates-to before the epic-target heuristic. Add a unit test
covering both halves of the bug and a CLI regression that mirrors the
#3936 repro using `bd list`.

_claude-opus-4-7-xhigh on behalf of matt wilkie_

* fix(mcp): route validate and detect-pollution to bd doctor (GH#4037)

The beads-mcp admin tool's validate action called bd validate which
does not exist as a standalone command. Similarly, detect-pollution
called bd detect-pollution which is also not a standalone command.

Both are subcommands of bd doctor:
- validate → bd doctor --check=validate [--fix --yes]
- detect-pollution → bd doctor --check=pollution [--clean --yes]

Map the MCP client methods to the correct bd doctor invocations.

* test(mcp): cover bd doctor routing for validate/detect_pollution

Adds tests asserting validate() and detect_pollution() build the expected
'bd doctor --check=...' argv (GH#4037), including --fix/--yes and --clean/--yes
flag forwarding. Also documents that the legacy validate(checks=...) arg is no
longer forwarded — bd doctor has no subset-selection flag — so the docstring no
longer advertises behavior the routing cannot honor.

Co-authored-by: kev <kglynn@pryoninc.com>

* fix(schema): panic on duplicate migration version numbers

list() now calls checkNoDuplicateVersions after parsing filenames; the
panic names both colliding files so the developer knows exactly what to
renumber. A companion CI job in ci.yml catches the same collision at PR
time with a shell glob — no Go build required.

Two empirical incidents (versions 0033 and 0043) motivated this: a
second branch claimed the same 'next free' number, merged cleanly at
the git level (no textual conflict), but silently under-applied the
second migration because INSERT IGNORE records only one version row.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(setup): clarify symlinked agents skip

Warn on stderr with the symlink target and a concrete recovery path when setup skips managed-section injection through an agent-file symlink. This keeps the corruption guard explicit without pretending the integration text was written.

Also cover the Lstat inspect-error branch called out in PR review coverage.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* debug config discovery and issue prefix failures (#4191)

* debug config discovery and issue prefix failures

Add debug-only diagnostics for missing config discovery and issue_prefix lookup failures, and make the missing-prefix error point users toward the canonical issue-prefix YAML key.\n\nRefs gastownhall/beads#3927\n\n_codex-gpt-5.5-medium on behalf of matt wilkie_

* clarify issue-prefix viper test intent

Rename the YAML key test to make clear it pins viper behavior, and explain why the diagnostics depend on the distinction.\n\n_codex-gpt-5.5-medium on behalf of matt wilkie_

---------

Co-authored-by: CI Bot <ci@beads.test>

* fix(codex): ship hook release safely

Move Codex native hook metadata under the Codex-only plugin directory so Claude keeps using only its inline `bd prime` hooks.

Leave release version bumps to the maintainer release flow while documenting hook metadata isolation under Unreleased.

Refs #3924.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* fix(setup): harden symlink follow-up handling

Refuse setup atomic writes through symlinks unless a caller explicitly opts into following the link, and report partial Claude/Mux setup when agent instructions are skipped.

Document repair steps for repositories already affected by corrupted mode-120000 CLAUDE.md entries.

Refs #3722.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* /{cmd,internal}: fix proxied server path

* /{docs,website}: gen docs

* flake.lock: Update (#4143)

Co-authored-by: steveyegge <613744+steveyegge@users.noreply.github.com>

* build(deps): bump goreleaser/goreleaser-action from 7.2.1 to 7.2.2 (#4109)

Bumps [goreleaser/goreleaser-action](https://github.com/goreleaser/goreleaser-action) from 7.2.1 to 7.2.2.
- [Release notes](https://github.com/goreleaser/goreleaser-action/releases)
- [Commits](https://github.com/goreleaser/goreleaser-action/compare/1a80836c5c9d9e5755a25cb59ec6f45a3b5f41a8...5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89)

---
updated-dependencies:
- dependency-name: goreleaser/goreleaser-action
  dependency-version: 7.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump fastmcp from 3.2.4 to 3.3.1 in /integrations/beads-mcp (#3957)

Bumps [fastmcp](https://github.com/PrefectHQ/fastmcp) from 3.2.4 to 3.3.1.
- [Release notes](https://github.com/PrefectHQ/fastmcp/releases)
- [Changelog](https://github.com/PrefectHQ/fastmcp/blob/main/docs/changelog.mdx)
- [Commits](https://github.com/PrefectHQ/fastmcp/compare/v3.2.4...v3.3.1)

---
updated-dependencies:
- dependency-name: fastmcp
  dependency-version: 3.3.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* /internal/storage/dbproxy/server: wip, external dolt server

* chore(deps): bump actions/create-github-app-token from 3.1.1 to 3.2.0 (#3956)

Bumps [actions/create-github-app-token](https://github.com/actions/create-github-app-token) from 3.1.1 to 3.2.0.
- [Release notes](https://github.com/actions/create-github-app-token/releases)
- [Changelog](https://github.com/actions/create-github-app-token/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/create-github-app-token/compare/1b10c78c7865c340bc4f6099eb2f838309f1e8c3...bcd2ba49218906704ab6c1aa796996da409d3eb1)

---
updated-dependencies:
- dependency-name: actions/create-github-app-token
  dependency-version: 3.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* build(deps): bump golang.org/x/sys from 0.43.0 to 0.45.0 (#3958)

* build(deps): bump golang.org/x/sys from 0.43.0 to 0.45.0

Bumps [golang.org/x/sys](https://github.com/golang/sys) from 0.43.0 to 0.45.0.
- [Commits](https://github.com/golang/sys/compare/v0.43.0...v0.45.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sys
  dependency-version: 0.44.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(nix): update vendorHash for go.sum changes

Auto-generated by .github/workflows/update-vendor-hash.yml in response to a dependabot Go-module bump (beads-bz5).

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* /{cmd,internal}: external server

* /{docs,website}: gen docs for new flags

* /internal/storage/{dbproxy,uow}: external uow provider

* /{cmd,docs,internal,website}: wire up

* /cmd/bd/init.go: disable again

* build(deps): bump github.com/anthropics/anthropic-sdk-go from 1.37.0 to 1.45.0 (#3959)

* build(deps): bump github.com/anthropics/anthropic-sdk-go

Bumps [github.com/anthropics/anthropic-sdk-go](https://github.com/anthropics/anthropic-sdk-go) from 1.37.0 to 1.45.0.
- [Release notes](https://github.com/anthropics/anthropic-sdk-go/releases)
- [Changelog](https://github.com/anthropics/anthropic-sdk-go/blob/main/CHANGELOG.md)
- [Commits](https://github.com/anthropics/anthropic-sdk-go/compare/v1.37.0...v1.45.0)

---
updated-dependencies:
- dependency-name: github.com/anthropics/anthropic-sdk-go
  dependency-version: 1.43.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore(nix): update vendorHash for go.sum changes

Auto-generated by .github/workflows/update-vendor-hash.yml in response to a dependabot Go-module bump (beads-bz5).

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* /{cmd,internal}: surface mismatches

* /{cmd,docs,internal,website}: remove pass

* /internal/storage/dbproxy/server: fix test

* cleanup: remove unused private helpers (#4205)

Remove two zero-call helpers found during the pattern-collapse inventory pass.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* docs: regenerate llms-full

Regenerate website/static/llms-full.txt so the main-branch doc freshness check matches the generated website docs after #4207.

Why: upstream main CI was failing in `Check doc flags freshness` because `llms-full.txt` was stale.

Verification:
- `CGO_ENABLED=0 go build -tags gms_pure_go -o bd ./cmd/bd/`
- `./scripts/check-doc-flags.sh ./bd`
- `./scripts/check-doc-freshness.sh`
- PR #4209 CI passed

_codex-gpt-5.5-medium on behalf of maphew_

* test(where): isolate initialized db path state (#4193)

Why: the where test could inherit BEADS_DB/BD_DB/BEADS_DIR state and resolve the project .beads path instead of its initialized temporary database.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* docs: finish Dolt and release doc consolidation

Main already converted docs/DOLT-BACKEND.md and docs/RELEASING.md into canonical pointer stubs. Finish the consolidation by moving the still-useful verified details into the authoritative docs and fixing stale inbound references that still sent readers to the pointer files.

DOLT.md keeps the server-mode Dolt install notes, remote setup guidance, macOS LaunchAgent setup, direct Dolt CLI usage, accidental data-directory commit cleanup, and the embedded-mode data path correction.

RELEASING.md keeps the release.sh easy path and Docusaurus versioning steps from the retired docs-level guide. AGENT_INSTRUCTIONS.md now links directly to the root releasing guide, and the init error path now links to the canonical Dolt guide under gastownhall/beads.

Refs: GH#3683, PR#3706

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

_codex-gpt-5.5-medium on behalf of matt wilkie_

* debug missing dolt database metadata (#4202)

* debug missing dolt database metadata

When metadata.json exists without dolt_database, bd silently falls back to the default database name. Log the config discovery state under debug output so non-default database drift can be diagnosed without changing normal behavior.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* test config debug default database guard

_codex-gpt-5.5-medium on behalf of matt wilkie_

---------

Co-authored-by: CI Bot <ci@beads.test>

* docs: record CI cleanup plan

* import: skip stale JSONL issue snapshots (#4204)

* import: skip stale JSONL issue snapshots

Skip imported JSONL issue records when the local row has a newer updated_at timestamp, and report stale skips without creating no-op commits.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* import: report stale skipped JSONL IDs

Expose stale_skipped_ids in import results so JSON callers can distinguish imported IDs from records that were intentionally skipped as stale.

_codex-gpt-5.5-medium on behalf of maphew_

* fix(close): support per-id reasons (#4194)

* fix(close): support per-id reasons

Why: upstream GH#3279 reports repeated --reason values applying incorrectly across multi-ID close, while GH#3268 needs a ready zero-dependency regression guard to prevent queue drift.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* docs: refresh close CLI references

_codex-gpt-5.5-medium on behalf of CI Bot_

* fix(close): preserve empty reason compatibility

Ignore empty --reason occurrences so explicit empty strings continue to fall back to the default close reason and do not conflict with --reason-file. Clarify that repeated reasons map to IDs by positional index.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* fix(install): use declared module path for go fallback (#4215)

The repository has moved to gastownhall/beads, but released Go modules still declare github.com/steveyegge/beads. The installer fallback should use the declared module path, and the docs/check should keep future guidance from regressing.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* fix(dolt): detect managed handoff port conflicts (#4217)

Avoid persisting environment-supplied managed Dolt ports into the repo-local port file, and add a doctor warning plus recovery docs for standalone-to-managed-city handoff conflicts.

_codex-gpt-5.5-high on behalf of matt wilkie_

* test(create): guard external-ref prelinking (#4216)

* test(create): guard external-ref prelinking

GH#3254 is already implemented on current main, but a narrow builder-level test makes the explicit external_ref pre-linking behavior harder to regress while the larger Linear roadmap remains open.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* test(create): avoid nil deref in ExternalRef failure-path Fatalf

Print the pointer instead of dereferencing it in the failure branch
of TestBuildCreateIssueEmptyExternalRefIsNil so the test does not
nil-deref while reporting a failure.

_claude-opus-4-7-medium on behalf of matt wilkie_

* test: improve coverage signal policy (#4219)

_codex-gpt-5.5-medium on behalf of maphew_

* fix(hooks): bound prime and hook waits

_codex-gpt-5.5-xhigh on behalf of CI Bot_

* fix(hooks): address review timeout edge cases

Scope generated-hook SIGALRM handling to the Perl alarm fallback, treat non-positive BEADS_PRIME_TIMEOUT values as the default bound, and remove dead GIT_HOOKS_PATH hook suppression in favor of git -c core.hooksPath=.

Also document the broad beads-repo hook suppression behavior in the changelog and add focused tests for the reviewed edge cases.

_codex-gpt-5.5-medium on behalf of matt wilkie_

* fix(schema): renumber duplicate release migrations

The release batch merged two schema PRs that reused existing migration version prefixes on main. Renumber the newly landed widening migrations so the duplicate-version CI gate can pass before release.

_codex-gpt-5.5-medium on behalf of maphew_

* fix(auto-import): make upgrade-recovery fallback non-destructive (#3955) (#3960)

* fix(auto-import): make upgrade-recovery fallback non-destructive (#3955)

The auto-import server-mode fallback imported JSONL via INSERT … ON
DUPLICATE KEY UPDATE, so if the emptiness guard in maybeAutoImportJSONL
ever regressed again (cf. PR #3630) a stale issues.jsonl could clobber
live Dolt rows on every command.

Add BatchCreateOptions.ConflictSkip (insert-if-new instead of UPSERT)
and thread it ONLY through the auto-import fallback seam via a dedicated
importFromLocalJSONLConflictSkip. Explicit `bd import`, `bd bootstrap`,
and `bd init --from-jsonl` keep UPSERT semantics. Now a guard regression
degrades to a harmless no-op instead of data loss.

Defense-in-depth (Layer 2) follow-up to incident #3948 — NOT the
incident fix itself, which is the v1.0.5 release cut (#3870).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* fix(auto-import): harden embedded ImportJSONLData with ConflictSkip (GH#3955)

Defense-in-depth increment on top of the server-mode fallback work. The
embedded EmbeddedDoltStore.ImportJSONLData fast-path is the primary
auto-import route for 1.0+ users and was gated only by its in-transaction
emptiness check. Make it insert-if-new (ConflictSkip) too, so a regression
in that check cannot clobber live rows — matching the server-mode fallback
and completing the #3955 guarantee across both auto-import paths.

Maintainer addition to PR #3960; original conflict-skip design and tests
by Emmanuel Sciara (esciara).

Agent-Signature: claude-opus-4-8-high on behalf of maphew
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Co-authored-by: matt wilkie <maphew@gmail.com>

* docs: add conservative Beads agent profile (#4220)

* docs: add conservative beads agent profile

_codex-gpt-5.5-medium on behalf of maphew_

* docs: tighten agent-profile wording and cover no-push branches

Address PR #4220 review:

- Drop noisy "Profile model:" prefix from the default-branch profile
  rule; lead with the…
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.

bd init --from-jsonl regenerates comments[].id on every import; live API uses UUIDv7 but importer uses UUIDv4

2 participants