The skill covers the four things that bit us today: the per-clone git config the driver needs (.gitattributes names it, but the driver lives in git config, which a checkout does not carry -- without it git silently line-merges), how to recover a file that already has markers in it, where genuine same-issue divergence is reconciled (bd conflicts, against Dolt -- not reconstructed from the JSONL), and the autostash trap that writes markers without anyone running `git stash`. Driver verified against a real conflict: plain text merge produced 3 marker lines and 3 invalid JSONL lines; the driver produced 470 rows, 0 invalid, and left the database byte-identical (470 rows before and after). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FC2jVn8Au7E9JPkw6bwXXH
4.2 KiB
name, description
| name | description |
|---|---|
| beads-merge | Resolve or prevent git conflicts in .beads/issues.jsonl, and reconcile genuinely divergent issues. Use when a merge, rebase, cherry-pick or stash pop conflicts on that file, when auto-export reports "invalid character", or when setting up a fresh clone. |
Merging .beads/issues.jsonl
The database is the truth; the file is derived. .beads/issues.jsonl is a
passive export of the local Dolt DB, which syncs over refs/dolt/data. Nothing
here ever merges the file — it regenerates it.
The one rule
Never hand-edit it, never git checkout it (that fires the post-checkout
hook and desynchronises export-state.json), and never resolve its conflict
markers by editing. Use bd and regenerate.
Setup — required once per clone
.gitattributes names the driver; the driver itself lives in git config, which
is per-clone and not carried by a checkout. Without this, git silently falls
back to line-merging and writes conflict markers into the JSONL.
git config merge.beads.name "beads issues.jsonl: import both sides, regenerate from the database"
git config merge.beads.driver "$PWD/scripts/beads-merge-driver.sh %O %A %B %P"
git check-attr merge -- .beads/issues.jsonl # must print "merge: beads"
A worktree shares .git/config with its parent, so a worktree needs no separate
setup — but a fresh git clone does.
What the driver does, and why it needs no merge logic
scripts/beads-merge-driver.sh imports both sides and re-exports. It writes
no merge logic of its own because bd import already has the semantics:
- an id the DB has not seen is created
- a row whose
updated_atis strictly newer than the local one wins - an older row is skipped (reported as
stale_skipped_ids) - a tie keeps every local column —
updated_athas second granularity, so a same-second tie can be two distinct updates, and local wins
So neither import can move the database backwards, which is why importing ours as well as theirs is safe: it restores a database that has lost rows without overwriting one that is ahead.
Set BEADS_MERGE_PULL=1 to bd dolt pull first (remote via BEADS_MERGE_REMOTE,
default gh). Off by default: a merge driver that reaches the network fails in
ways a merge cannot report.
Reconciling a genuinely divergent issue
The driver resolves by recency, which is right for the common case (two sides filing different issues) and wrong when both sides meaningfully edited the same issue — the older edit is skipped silently.
Do not reconstruct that from the JSONL. bd already does it field by field
against the Dolt working set, which is the real sync path:
bd conflicts list # which issues are conflicted
bd conflicts show improvise-XXXX # base / ours / theirs, per field
bd conflicts resolve improvise-XXXX --ours # or --theirs
bd conflicts resolve --all --theirs
If the driver already took the newer side and you need the other, the losing
content is in git history: git show <their-commit>:.beads/issues.jsonl, find
the row, and apply the fields with bd update. Never import a whole stale file
to recover one issue — that is what --allow-stale does, and it overwrites every
newer local row.
Recovering a corrupted file
If auto-export reports failed to compare existing JSONL against local store: invalid character '<', the file has conflict markers in it and auto-export has
been silently skipping — possibly for many commits.
grep -nE '^(<<<<<<<|=======|>>>>>>>)' .beads/issues.jsonl # confirm
bd export -o .beads/issues.jsonl # regenerate from the DB
Check whether the markers reached a commit (git log -S '<<<<<<<' -- .beads/issues.jsonl).
They are usually working-tree only.
The trap that caused this
A git stash pop conflict writes <<<<<<< Updated upstream /
>>>>>>> Stashed changes into the file. Git's autostash during a dirty-tree
merge or rebase does this without anyone running stash explicitly, and the
orphaned entry stays in git stash list. The merge driver covers stash pops too,
but the habit that avoids it is: commit before merging, and treat a stray
autostash in git stash list as a live hazard.