I Built a Distributed Issue Tracker I Didn't Need

March 22, 2026

Introduction

If you've read my post about finding the right AI workflow, you know the path that got me here: I started with the Ralph Wiggum loop and markdown implementation plans, moved to PRD JSON files, then tried GitHub Issues as the source of truth. Each step solved something but introduced new friction. GitHub Issues gave me visibility but created micro-ticket hell, 15-20 tiny issues per feature drowning out real bugs.

Then I found beads, a git-native issue tracker that stores tasks right inside your repo. No external service, no dashboard, no sync problems. Just a CLI and some JSONL files living next to your code. It was exactly what I needed: git-native, AI-friendly, no UI pollution.

This is the story of how I took a simple CLI tool and spent six weeks turning it into a multi-repo, multi-machine sync system. Only to discover I never needed any of it.

First Contact

I set up beads in my main project, eniem, a monorepo with a Next.js app, a docs site, and a CLI tool.

bd init

That gave me a .beads/ folder with a config file, a SQLite database, and JSONL files for issues. I could create issues from the terminal, close them when done, and everything lived in git.

bd create "Fix OAuth callback on mobile"
bd ready        # what's next?
bd close eni-3  # done

No context switching. No browser. Just me and my terminal. It felt right.

Then I opened a pull request and saw it: my PR diff was polluted with .beads/issues.jsonl changes. Every issue create or close touched the JSONL file, and those changes ended up on my feature branches.

The fix was a sync-branch, a dedicated beads-sync branch where all beads commits go. Feature branches stay clean.

bd config set sync.branch beads-sync

I paired this with git hooks: pre-commit to flush pending changes, post-merge to import after pull, post-checkout to import after switching branches. Beads became invisible during development but always up to date.

This is where a reasonable person would have stopped. I didn't.

Going Multi-Repo

Once I trusted the workflow in eniem, I added beads to my other projects: fal-cli (image generation CLI) and spawner (process manager). Each got its own .beads/ folder, its own issues, its own prefix.

Now I had a new problem: to see all my open issues, I had to cd into each project and run bd list. Three repos, three mental contexts, three places to check.

I also noticed .beads/ was showing up in .gitignore across repos. Not a big deal, but collaborators would see the entry and wonder what it was. Beads has a stealth mode that solves this. Instead of .gitignore (tracked by git), it uses .git/info/exclude, a local-only exclusion file that never appears in commits:

bd init --stealth --prefix fal

Zero footprint. I went back and converted all my projects. Opened PRs to remove the .beads/ lines from each .gitignore. Clean.

Then came the hub, beads' answer to the "one view across all projects" problem. A dedicated repo that pulls issues from all your projects into one database:

mkdir beads-hub && cd beads-hub
git init
bd init --prefix hub
 
bd repo add ../eniem
bd repo add ../fal-cli
bd repo add ../spawner
bd repo sync

Now from the hub, I could see everything:

$ bd list
 spw-3gu [P2] - Fix process cleanup on SIGTERM
 fal-agk [P2] - Add batch generation support
 eni-4m1 [P2] - Update OAuth docs for v2

Each issue carries its project prefix (eni, fal, spw) so there's never confusion about where something belongs. I had the setup I wanted. Or so I thought.

The Sync Reality

After the hub was wired up, I had this picture in my head: I create an issue in any project, and it magically appears on my VPS. The daemon handles everything. Set and forget.

That's not what happens.

I created a bead in eniem. Checked the hub. Nothing. Waited five minutes. Still nothing. Checked GitHub. Nothing pushed. The daemon was running, the hub was wired, everything looked healthy. But the issue was stuck in eniem's SQLite database, invisible to the rest of the system.

It took me hours of debugging (reading daemon logs, testing every combination of sync commands, watching file timestamps) to understand the real data flow. Here's what's actually going on:

CommandWhereWhat it does
bd create "title"projectCreates an issue in the project's SQLite DB
bd close <id>projectCloses an issue in the project's SQLite DB
bd syncprojectFlushes SQLite DB to .beads/issues.jsonl
bd repo synchubReads all projects' JSONL files into the hub's DB
bd sync --fullhubCommits + pushes hub to GitHub
bd daemon starthubBackground process that auto-pulls from GitHub

The critical gap: bd create writes to SQLite, but the hub reads JSONL. Nothing bridges that automatically. bd sync flushes SQLite to JSONL, but nobody runs it for you. bd repo sync imports JSONL into the hub, but the daemon doesn't do this either. And bd sync --full commits and pushes to GitHub, but the daemon doesn't reliably handle external changes.

Every link in the chain requires a manual command. The daemon's real contribution is one thing: auto-pulling from GitHub on the receiving machine. That's it.

When you see --auto-commit --auto-push --auto-pull on the daemon, you expect it to handle the full lifecycle. It doesn't. The --auto-commit flag only commits the daemon's own exports, not changes made by bd repo sync. The naming is misleading, and the gap between what you'd expect and what actually works cost me an entire afternoon.

The actual workflow after any bead change:

# 1. In the project, flush SQLite to JSONL:
bd sync
 
# 2. In the hub, import from all projects + push to GitHub:
cd ~/ws/dev/beads-hub && bd repo sync && bd sync --full

Two commands. Always. Don't rely on the daemon to push.

And one more thing I discovered late: the hub is a read-only aggregator. When I create a bead in eniem on my Mac, flush it to the hub, and push to GitHub, the VPS hub pulls it down. But if I cd ~/ws/dev/eniem on the VPS and run bd list, that issue isn't there. It only exists in the hub's database.

projects → hub → GitHub → other hub
                                ↛ other projects

The hub collects from projects but never distributes back. Each project's .beads/ is an isolated island. The hub is the only place where you see everything. It's a dashboard, not the distributed sync system I was building toward.

The Dolt Detour

When I set up the hub, beads was at v0.49.6. SQLite backend, JSONL files, a daemon. It worked. Then I ran brew upgrade beads and got v0.61.0. Everything broke.

v0.50 replaced SQLite with Dolt, a version-controlled SQL database. In practice, this meant every project needed a Dolt SQL server running in the background, the daemon was removed entirely, and bd repo sync still expected JSONL files that Dolt didn't write. Multi-repo hub sync was effectively broken.

I wasn't alone. Issue #2573 ("Moving to Dolt pretty much made beads unusable for me") had 13+ comments from users leaving the tool. A PR for auto-export from Dolt to JSONL was proposed but closed.

I downgraded to v0.49.6 on both machines. Nuked all .beads/ directories, re-initialized with SQLite, re-wired the hub. Twenty minutes once I knew what I was doing. The lesson: don't upgrade beads past v0.49.x if you use multi-repo hubs.

To install v0.49.6 specifically:

gh release download v0.49.6 --repo steveyegge/beads \
  --pattern "beads_0.49.6_$(uname -s | tr A-Z a-z)_$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/').tar.gz" \
  --dir /tmp
tar xzf /tmp/beads_*.tar.gz -C /tmp
cp /tmp/bd ~/.local/bin/bd
chmod +x ~/.local/bin/bd

The Part I Should Have Read First

After everything (the stealth mode migration, the hub setup, the Dolt detour, the sync debugging, the one-way street discovery) I went digging into why the multi-repo hub exists in the first place.

Turns out, the beads docs are pretty clear about this:

"You DON'T need multi-repo if: working solo on your own project."

The multi-repo hub was built for three use cases, none of which are mine:

  • OSS contributor isolation: keeping your personal planning notes separate from upstream when contributing to a project that already uses beads.
  • Multi-agent orchestration: Steve Yegge's "Gas Town" vision, where parallel AI agents coordinate across worktrees through a shared issue graph.
  • Team collaboration: separating personal planning from shared project issues with role-based routing.

I'm a solo developer. Working on my own projects. On two machines. The simplest setup would have been: one beads repo, all issues in one place, daemon syncs to GitHub, done. No per-project stealth init. No JSONL bridge. No two-step sync dance.

I built a distributed system to solve a problem that a single folder would have fixed.

But I don't regret it. I now understand beads deeply: the sync model, the daemon's real capabilities, the Dolt migration landscape, and the multi-repo architecture. And if I ever need contributor isolation or multi-agent coordination, the infrastructure is documented and ready.

For now, though? One beads project. Prefixes for organization. Daemon for sync. Keep it simple.