- Replace beads-metadata with beads-sync throughout docs (matches code default and internal examples) - Fix bd daemon start → bd daemon --start (correct flag syntax) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
17 KiB
Protected Branch Workflow
This guide explains how to use beads with protected branches on platforms like GitHub, GitLab, and Bitbucket.
Table of Contents
Overview
Problem: GitHub and other platforms let you protect branches (like main) to require pull requests for all changes. This prevents beads from auto-committing issue updates directly to main.
Solution: Beads can commit to a separate branch (like beads-sync) using git worktrees, while keeping your main working directory untouched. Periodically merge the metadata branch back to main via a pull request.
Benefits:
- ✅ Works with any git platform's branch protection
- ✅ Main branch stays protected
- ✅ No disruption to your primary working directory
- ✅ Backward compatible (opt-in via config)
- ✅ Minimal disk overhead (uses sparse checkout)
- ✅ Platform-agnostic solution
Quick Start
1. Initialize beads with a separate sync branch:
cd your-project
bd init --branch beads-sync
This creates a .beads/ directory and configures beads to commit to beads-sync instead of main.
Important: After initialization, you'll see some untracked files that should be committed to your protected branch:
# Check what files were created
git status
# Commit the beads configuration to your protected branch
git add .beads/.gitignore .gitattributes
git commit -m "Initialize beads issue tracker"
git push origin main # Or create a PR if required
Files created by bd init --branch:
Files that should be committed to your protected branch (main):
.beads/.gitignore- Tells git what to ignore in .beads/ directory.gitattributes- Configures merge driver for intelligent JSONL conflict resolution
Files that are automatically gitignored (do NOT commit):
.beads/beads.db- SQLite database (local only, regenerated from JSONL).beads/daemon.lock,daemon.log,daemon.pid- Runtime files.beads/beads.left.jsonl,beads.right.jsonl- Temporary merge artifacts
The sync branch (beads-sync) will contain:
.beads/issues.jsonl- Issue data in JSONL format (committed automatically by daemon).beads/metadata.json- Metadata about the beads installation.beads/config.yaml- Configuration template (optional)
2. Start the daemon with auto-commit:
bd daemon --start --auto-commit
The daemon will automatically commit issue changes to the beads-sync branch.
3. When ready, merge to main:
# Check what's changed
bd sync --status
# Merge to main (creates a pull request or direct merge)
bd sync --merge
That's it! The complete workflow is described below.
How It Works
Git Worktrees
Beads uses git worktrees to maintain a lightweight checkout of your sync branch. Think of it as a mini git clone that shares the same repository history.
Directory structure:
your-project/
├── .git/ # Main git directory
│ └── beads-worktrees/
│ └── beads-sync/ # Worktree (only .beads/ checked out)
│ └── .beads/
│ └── issues.jsonl
├── .beads/ # Your main copy
│ ├── beads.db
│ ├── issues.jsonl
│ └── .gitignore
├── .gitattributes # Merge driver config (in main branch)
└── src/ # Your code (untouched)
What lives in each branch:
Main branch (protected):
.beads/.gitignore- Tells git what to ignore.gitattributes- Merge driver configuration
Sync branch (beads-sync):
.beads/issues.jsonl- Issue data (committed by daemon).beads/metadata.json- Repository metadata.beads/config.yaml- Configuration template
Not tracked (gitignored):
.beads/beads.db- SQLite database (local only).beads/daemon.*- Runtime files
Key points:
- The worktree is in
.git/beads-worktrees/(hidden from your workspace) - Only
.beads/is checked out in the worktree (sparse checkout) - Changes to issues are committed in the worktree
- Your main working directory is never affected
- Disk overhead is minimal (~few MB for the worktree)
Automatic Sync
When you update an issue:
- Issue is updated in
.beads/beads.db(SQLite database) - Daemon exports to
.beads/issues.jsonl(JSONL file) - JSONL is copied to worktree (
.git/beads-worktrees/beads-sync/.beads/) - Daemon commits the change in the worktree to
beads-syncbranch - Main branch stays untouched (no commits on
main)
Setup
Option 1: Initialize New Project
cd your-project
bd init --branch beads-sync
This will:
- Create
.beads/directory with database - Set
sync.branchconfig tobeads-sync - Import any existing issues from git (if present)
- Prompt to install git hooks (recommended: say yes)
Option 2: Migrate Existing Project
If you already have beads set up and want to switch to a separate branch:
# Set the sync branch
bd config set sync.branch beads-sync
# Start the daemon (it will create the worktree automatically)
bd daemon --start --auto-commit
Daemon Configuration
For automatic commits to the sync branch:
# Start daemon with auto-commit
bd daemon --start --auto-commit
# Or with auto-commit and auto-push
bd daemon --start --auto-commit --auto-push
Daemon modes:
--auto-commit: Commits to sync branch after each change--auto-push: Also pushes to remote after each commit- Default interval: 5 seconds (check for changes every 5s)
Recommended: Use --auto-commit but not --auto-push if you want to review changes before pushing. Use --auto-push if you want fully hands-free sync.
Environment Variables
You can also configure the sync branch via environment variable:
export BEADS_SYNC_BRANCH=beads-sync
bd daemon --start --auto-commit
This is useful for CI/CD or temporary overrides.
Daily Workflow
For AI Agents
AI agents work exactly the same way as before:
# Create issues
bd create "Implement user authentication" -t feature -p 1
# Update issues
bd update bd-a1b2 --status in_progress
# Close issues
bd close bd-a1b2 "Completed authentication"
All changes are automatically committed to the beads-sync branch by the daemon. No changes are needed to agent workflows!
For Humans
Check status:
# See what's changed on the sync branch
bd sync --status
This shows the diff between beads-sync and main (or your current branch).
Manual commit (if not using daemon):
bd sync --flush-only # Export to JSONL and commit to sync branch
Pull changes from remote:
# Pull updates from other collaborators
bd sync --no-push
This pulls changes from the remote sync branch and imports them to your local database.
Merging Changes
Option 1: Via Pull Request (Recommended)
For protected branches with required reviews:
# 1. Push your sync branch
git push origin beads-sync
# 2. Create PR on GitHub/GitLab/etc.
# - Base: main
# - Compare: beads-sync
# 3. After PR is merged, update your local main
git checkout main
git pull
bd import # Import the merged changes
Option 2: Direct Merge (If Allowed)
If you have push access to main:
# Check what will be merged
bd sync --merge --dry-run
# Merge sync branch to main
bd sync --merge
# This will:
# - Switch to main branch
# - Merge beads-sync with --no-ff (creates merge commit)
# - Push to remote
# - Import merged changes to database
Safety checks:
- ✅ Verifies you're not on the sync branch
- ✅ Checks for uncommitted changes in working tree
- ✅ Detects merge conflicts and provides resolution steps
- ✅ Uses
--no-fffor clear history
Merge Conflicts
If you encounter conflicts during merge:
# bd sync --merge will detect conflicts and show:
Error: Merge conflicts detected
Conflicting files:
.beads/issues.jsonl
To resolve:
1. Fix conflicts in .beads/issues.jsonl
2. git add .beads/issues.jsonl
3. git commit
4. bd import # Reimport to sync database
Resolving JSONL conflicts:
JSONL files are append-only and line-based, so conflicts are rare. When they occur:
- Open
.beads/issues.jsonland look for conflict markers (<<<<<<<,=======,>>>>>>>) - Both versions are usually valid - keep both lines
- Remove the conflict markers
- Save and commit
Example conflict resolution:
<<<<<<< HEAD
{"id":"bd-a1b2","title":"Feature A","status":"closed","updated_at":"2025-11-02T10:00:00Z"}
=======
{"id":"bd-a1b2","title":"Feature A","status":"in_progress","updated_at":"2025-11-02T09:00:00Z"}
>>>>>>> beads-sync
Resolution: Keep the line with the newer updated_at:
{"id":"bd-a1b2","title":"Feature A","status":"closed","updated_at":"2025-11-02T10:00:00Z"}
Then:
git add .beads/issues.jsonl
git commit -m "Resolve issues.jsonl merge conflict"
bd import # Import to database (will use latest timestamp)
Troubleshooting
"fatal: refusing to merge unrelated histories"
This happens if you created the sync branch independently. Merge with --allow-unrelated-histories:
git merge beads-sync --allow-unrelated-histories --no-ff
Or use bd sync --merge which handles this automatically.
"worktree already exists"
If the worktree is corrupted or in a bad state:
# Remove the worktree
rm -rf .git/beads-worktrees/beads-sync
# Prune stale worktree entries
git worktree prune
# Restart daemon (it will recreate the worktree)
bd daemon --stop && bd daemon --start
"branch 'beads-sync' not found"
The sync branch doesn't exist yet. The daemon will create it on the first commit. If you want to create it manually:
git checkout -b beads-sync
git checkout main # Switch back
Or just let the daemon create it automatically.
"Cannot push to protected branch"
If the sync branch itself is protected:
- Option 1: Unprotect the sync branch (it's metadata, doesn't need protection)
- Option 2: Use
--auto-commitwithout--auto-push, and push manually when ready - Option 3: Use a different branch name that's not protected
Daemon won't start
Check daemon status and logs:
# Check status
bd daemon --status
# View logs
tail -f ~/.beads/daemon.log
# Restart daemon
bd daemon --stop && bd daemon --start
Common issues:
- Port already in use: Another daemon is running
- Permission denied: Check
.beads/directory permissions - Git errors: Ensure git is installed and repository is initialized
Changes not syncing between clones
Ensure all clones are configured the same way:
# On each clone, verify:
bd config get sync.branch # Should be the same (e.g., beads-sync)
# Pull latest changes
bd sync --no-push
# Check daemon is running
bd daemon --status
FAQ
Do I need to configure anything on GitHub/GitLab?
No! This is a pure git solution that works on any platform. Just protect your main branch as usual.
Can I use a different branch name?
Yes! Use any branch name you want:
bd init --branch my-custom-branch
# or
bd config set sync.branch my-custom-branch
Can I change the branch name later?
Yes:
bd config set sync.branch new-branch-name
bd daemon --stop && bd daemon --start
The old worktree will remain (no harm), and a new worktree will be created for the new branch.
What if I want to go back to committing to main?
Unset the sync branch config:
bd config set sync.branch ""
bd daemon --stop && bd daemon --start
Beads will go back to committing directly to your current branch.
Does this work with multiple collaborators?
Yes! Each collaborator configures their own sync branch:
# All collaborators use the same branch
bd config set sync.branch beads-sync
Everyone's changes sync via the beads-sync branch. Periodically merge to main via PR.
How often should I merge to main?
This depends on your workflow:
- Daily: If you want issue history in
mainfrequently - Per sprint: If you batch metadata updates
- As needed: Only when you need others to see issue updates
There's no "right" answer - choose what fits your team.
Can I review changes before merging?
Yes! Use bd sync --status to see what's changed:
bd sync --status
# Shows diff between beads-sync and main
Or create a pull request and review on GitHub/GitLab.
What about disk space?
Worktrees are very lightweight:
- Sparse checkout means only
.beads/is checked out - Typically < 1 MB for the worktree
- Shared git history (no duplication)
Can I delete the worktree?
Yes, but the daemon will recreate it. If you want to clean up permanently:
# Stop daemon
bd daemon --stop
# Remove worktree
git worktree remove .git/beads-worktrees/beads-sync
# Unset sync branch
bd config set sync.branch ""
Does this work with bd sync?
Yes! bd sync works normally and includes special commands for the merge workflow:
bd sync --status- Show diff between branchesbd sync --merge- Merge sync branch to mainbd sync --merge --dry-run- Preview merge
Can AI agents merge automatically?
Not recommended! Merging to main is a deliberate action that should be human-reviewed, especially with protected branches. Agents should create issues and update them; humans should merge to main.
However, if you want fully automated sync:
# WARNING: This bypasses branch protection!
bd daemon --start --auto-commit --auto-push
bd sync --merge # Run periodically (e.g., via cron)
What if I forget to merge for a long time?
No problem! The sync branch accumulates all changes. When you eventually merge:
bd sync --merge
All accumulated changes will be merged at once. Git history will show the full timeline.
Can I use this with GitHub Actions or CI/CD?
Yes! Example GitHub Actions workflow:
name: Sync Beads Metadata
on:
schedule:
- cron: '0 0 * * *' # Daily at midnight
workflow_dispatch: # Manual trigger
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0 # Full history
- name: Install bd
run: |
curl -fsSL https://raw.githubusercontent.com/steveyegge/beads/main/scripts/install.sh | bash
- name: Pull changes
run: |
git fetch origin beads-sync
bd sync --no-push
- name: Merge to main (if changes)
run: |
if bd sync --status | grep -q 'ahead'; then
bd sync --merge
git push origin main
fi
Note: Make sure the GitHub Action has write permissions to push to main.
Platform-Specific Notes
GitHub
Protected branch settings:
- Go to Settings → Branches → Add rule
- Branch name pattern:
main - Check "Require pull request before merging"
- Save
Create sync branch PR:
git push origin beads-sync
gh pr create --base main --head beads-sync --title "Update beads metadata"
GitLab
Protected branch settings:
- Settings → Repository → Protected Branches
- Branch:
main - Allowed to merge: Maintainers
- Allowed to push: No one
Create sync branch MR:
git push origin beads-sync
glab mr create --source-branch beads-sync --target-branch main
Bitbucket
Protected branch settings:
- Repository settings → Branch permissions
- Branch:
main - Check "Prevent direct pushes"
Create sync branch PR:
git push origin beads-sync
# Create PR via Bitbucket web UI
Advanced Topics
Multiple Sync Branches
You can use different sync branches for different purposes:
# Development branch
bd config set sync.branch beads-dev
# Production branch
bd config set sync.branch beads-prod
Switch between them as needed.
Syncing with Upstream
If you're working on a fork:
# Add upstream
git remote add upstream https://github.com/original/repo.git
# Fetch upstream changes
git fetch upstream
# Merge upstream beads-sync to yours
git checkout beads-sync
git merge upstream/beads-sync
bd import # Import merged changes
Custom Worktree Location
By default, worktrees are in .git/beads-worktrees/. This is hidden and automatic. If you need a custom location, you'll need to manage worktrees manually (not recommended).
Migration Guide
From Direct Commits to Sync Branch
If you have an existing beads setup committing to main:
-
Set sync branch:
bd config set sync.branch beads-sync -
Restart daemon:
bd daemon --stop && bd daemon --start -
Verify:
bd config get sync.branch # Should show: beads-sync
Future commits will go to beads-sync. Historical commits on main are preserved.
From Sync Branch to Direct Commits
If you want to stop using a sync branch:
-
Unset sync branch:
bd config set sync.branch "" -
Restart daemon:
bd daemon --stop && bd daemon --start
Future commits will go to your current branch (e.g., main).
Need help? Open an issue at https://github.com/steveyegge/beads/issues