- Published on
12 Repos, One Migration: What I Learned Moving Beads to Dolt
I spent a day migrating Beads from its SQLite/JSONL backend to Dolt across all my active repositories. Here is what broke, what I learned, and why cloud agents still cannot use it.
I run Beads as my issue tracker across every active project. It is a CLI tool that keeps issues in a local database alongside the code, which means AI agents can read and update the tracker without leaving the terminal or calling an external API. I wrote about why I use it in my current AI dev setup post.
Last week Beads shipped 0.61.0, which switched the storage backend from SQLite and JSONL files to Dolt — a version-controlled relational database that speaks MySQL. The migration was supposed to be one command per repo: bd bootstrap. It mostly was. But "mostly" across 12 repositories turned out to be instructive.
Why the Backend Change Matters
The old system stored issues in a SQLite database and kept a issues.jsonl file in sync as the git-tracked backup. It worked, but the JSONL file was the source of truth for git, which created a category of problem: if two sessions wrote to the database concurrently, or if a pull happened while the daemon was running, you could end up with merge conflicts in the JSONL. Not catastrophic, but annoying.
Dolt is a different model. It is a full SQL database with git-style branching and commit history baked into the storage layer. Conflicts are handled at the cell level, not the file level. The JSONL file becomes a backup, not the source of truth. That is a meaningful improvement for any project where more than one agent session touches the tracker.
The Pilot Approach
I had 12 JSONL-backed repositories to migrate. Before touching all of them, I ran a proper pilot on one: this site's repo, stonebergdesigntwo. The goal was to validate three things:
- Local Mac workflow — does
bd bootstrapwork and do normal operations keep working? - Codex web — can a cloud agent read and write the tracker from a Codex session?
- Claude Code web — same question, different environment?
Local passed with one caveat: on a fresh terminal session, bd dolt show reports Port: 0 and the first write command fails with dolt circuit breaker is open. The workaround is bd dolt start && bd dolt test to stabilize the connection. It works, but it is not clean enough to call fully solved. I filed it as a bug.
Cloud validation is where things got interesting.
Cloud Agents Cannot Use Beads (Yet)
Neither Claude Code web nor Codex runs dolt or bd in the default sandbox environment. Every probe command came back command not found.
This is not a Beads problem — it is a sandbox problem. Both platforms support custom environments. Claude Code web has an "Add environment" option in the session UI; Codex has an Environments settings page. A startup script that downloads the dolt binary and the bd CLI would solve it. I have a custom environment configured some of my projects that I use as the default in Claude Code on Web or Codex on the Web but I want to generally try and set things up that work out of the box with default vendor configuration.
For now, the practical rule is: Beads is a local tool. Cloud agents cannot use bd commands, and the committed issues.jsonl is a snapshot that may lag the live Dolt database. The more reliable approach is to keep docs/READY_TASKS.md current by running bd ready > docs/READY_TASKS.md after local sessions — cloud agents read from that file and write new discoveries to docs/DISCOVERED_TASKS.md for local reconciliation. That is not a blocking limitation for most workflows — the local agent session does the heavy lifting.
What Broke During Migration
Running bd bootstrap then bd import .beads/issues.jsonl across 12 repos revealed four categories of schema drift that had accumulated since I set up Beads on each project.
1. The done Status
Two repos — jffp and onsand — had issues with "status": "done". Beads 0.61 only accepts open, in_progress, and closed. The import failed on the first done issue it hit.
onsand was the extreme case: all 39 issues had done status. The whole project had been wound down and every issue closed out using the old status name. A one-liner fixed it:
sed -i '' 's/"status":"done"/"status":"closed"/g' .beads/issues.jsonl
The interesting thing is I did not know this drift existed. The old backend accepted done silently. The migration surfaced it.
2. Numeric Comment IDs
junestore had 162 issues, 15 of which had embedded comments. The import failed with:
json: cannot unmarshal number into Go struct field Comment.comments.id of type string
The old format stored comment IDs as integers ("id": 10). The new backend expects strings ("id": "10"). A small Python script patched all 17 affected comment records and the import ran clean.
The deeper observation: this kind of type drift is invisible until you try to move the data somewhere that enforces a schema.
3. Stale Pre-Commit Hooks
Two repos had pre-commit hooks from much older Beads versions that no longer worked:
- One called
bd sync --flush-only— a command that was removed in 0.61 - One called
bd hook pre-commit— a command that was renamed tobd hooks run pre-commit
Both hooks failed silently in a way that blocked commits with an unhelpful error message. The fix was the same for both: bd hooks install, which rewrites the hook shim to use the current API. It takes two seconds and should probably be part of every upgrade checklist.
4. An Incomplete Prior Bootstrap
sdbiz was the most interesting case. Someone (a previous agent session, as it turned out) had started a Beads migration on this repo and left it in a half-finished state. The .beads/dolt/ directory existed, the Dolt server would start and report a successful connection, but the beads database had never been created inside it. Every bd command that tried to open the database failed.
bd bootstrap saw the existing dolt/ directory and the .bd-dolt-ok marker file and decided there was nothing to do. It had no way to know the database creation had not completed.
The fix: delete the incomplete dolt/ directory entirely, then re-run bootstrap. Since the beads database had never held any data — the source of truth was still in issues.jsonl — there was nothing to lose. The fresh bootstrap completed in seconds.
What the Migration Revealed
The useful frame for a migration like this is not "how much work was it" but "what did you find out."
What I found out: schema drift is real even in a single-developer setup. I set up Beads at different times across these repos, on different versions, with different habits. By the time I tried to move them all to a consistent backend, they had diverged in at least four distinct ways — none of which was visible during normal day-to-day use.
The migration created a snapshot of that drift and forced me to resolve it. Every repo is now on the same version, the same backend, the same status vocabulary, and the same hook format. That consistency will matter when I start doing more cross-repo work with agents.
The cloud limitation is the one thing that is still open. I am planning to solve it by building a custom environment with Dolt pre-installed, using the tennistour configuration as a template. When that is working I will write it up.
If you are using Beads and planning a 0.61 migration, the things most likely to break for you: old done statuses, numeric comment IDs if your JSONL is old, and stale pre-commit hooks. Check all three before you run the import.