How
br synckeeps your repository safe.
br (beads_rust) is a non-invasive issue tracker. The br sync command synchronizes your SQLite database with a JSONL file for git-based collaboration.
Key safety principle: br sync will never modify your source code or execute git commands.
| Operation | Description |
|---|---|
Export (--flush-only) |
Writes issues from SQLite to .beads/issues.jsonl |
Import (--import-only) |
Reads issues from JSONL into SQLite |
Status (--status) |
Shows sync state without modifying anything |
All file I/O is confined to the .beads/ directory by default.
These are explicit design non-goals. br will never:
- Execute git commands - No commits, no pushes, no staging
- Modify files outside
.beads/- Your source code is never touched - Install or invoke git hooks - Fully manual hook setup if desired
- Run as a daemon - Simple CLI only, no background processes
- Auto-commit changes - Every git operation requires explicit user action
- Connect to external services - Offline-first, no network calls
| Guard | What it prevents | Override |
|---|---|---|
| Empty DB guard | Exporting 0 issues over a JSONL with N issues | --force |
| Stale DB guard | Exporting when DB is missing issues from JSONL | --force |
| Guard | What it prevents | Override |
|---|---|---|
| Conflict marker scan | Importing unresolved merge conflicts | None - must resolve conflicts |
| Schema validation | Importing malformed JSON | None - must fix JSONL |
| Tombstone protection | Resurrecting deleted issues | None - by design |
The --force flag bypasses export safety guards. Use it only when you understand the consequences:
# Safe: Export after intentionally clearing the database
br sync --flush-only --force
# Safe: Import after confirming JSONL is authoritative
br sync --import-only --forceWhen to use --force:
- After a deliberate database reset
- When JSONL is known to be authoritative
- During recovery from corruption
When NOT to use --force:
- Routinely (defeats the purpose of guards)
- Without understanding why a guard triggered
- When the error message is unclear
By default, sync operates on .beads/issues.jsonl. To use a different path:
# Set via environment variable
export BEADS_JSONL=/path/to/issues.jsonl
br sync --flush-only --allow-external-jsonlPaths outside .beads/ require the explicit --allow-external-jsonl opt-in.
Backups: When exporting to a JSONL file that lives inside .beads/ (including custom
BEADS_JSONL paths that still target .beads/), br creates timestamped backups in
.beads/.br_history/ before overwriting.
Safety notes:
- External paths bypass the default confinement
- Symlinks pointing outside
.beads/are rejected - Paths are canonicalized before use
br sync --status # Check if import is needed
br sync --import-only # Import any JSONL changesbr sync --flush-only # Export DB changes to JSONL
git add .beads/ # Stage for commit (manual!)
git commit -m "Update issues"git pull
br sync --import-only # Import collaborators' changesCause: Your database has 0 issues, but the JSONL file has existing issues.
Fix:
- Run
br sync --import-onlyfirst to populate the database - Or use
--forceif you intentionally want an empty export
Cause: The JSONL file contains issues that don't exist in your database.
Fix:
- Run
br sync --import-onlyfirst to import the missing issues - Or use
--forceif you intentionally want to lose those issues
Cause: The JSONL file contains unresolved git merge conflicts.
Fix:
- Open the JSONL file and resolve the conflicts manually
- Look for
<<<<<<<,=======, and>>>>>>>markers --forcewill NOT bypass this check
The Go predecessor (bd) suffered a catastrophic failure where bd sync deleted all repository source files. This wasn't a theoretical risk—it actually happened, destroying irreplaceable work. The root cause was a sync operation that had too much authority: it could execute git commands, modify arbitrary files, and make irreversible changes without explicit confirmation.
This incident motivated every design decision in br's safety model.
br employs multiple layers of protection:
| Layer | Protection | Failure Mode Blocked |
|---|---|---|
| No git operations | Cannot execute git rm, git clean, or any git command |
Eliminates the primary attack vector from the original incident |
| Path confinement | All writes strictly confined to .beads/ directory |
Prevents accidental modification of source code, configs, or system files |
| Path validation | Rejects traversal (../), symlink escapes, and disallowed extensions |
Blocks path injection attacks and symlink-based escapes |
| Atomic writes | Uses temp file + rename; partial failures don't corrupt | Prevents data loss from interrupted operations |
| Safety guards | Empty DB and stale DB guards require --force to override |
Makes destructive operations explicit and intentional |
The safety model is backed by an extensive test suite (635+ tests) that ensures these guarantees cannot regress:
- Path guard unit tests (
sync::path::tests): 22 tests verify that traversal attempts, external paths, and disallowed file types are rejected - File tree snapshot tests (
e2e_sync_git_safety.rs): Integration tests take complete snapshots of the directory tree before and after sync, verifying that only.beads/issues.jsonland related files are touched - Git mutation tests: Regression tests verify that no commits, staged changes, or
.git/modifications occur during sync - Atomic write tests (
e2e_sync_failure_injection.rs): Tests inject failures mid-export to verify the original file is preserved - Conflict marker tests: Import preflight tests verify that merge conflicts are detected and rejected
When sync operations occur, structured logging records safety-critical decisions:
# Enable verbose logging to see safety checks
br sync --flush-only -v
br sync --flush-only -vv # Even more detailKey logged events:
- Path validation results (allowed/rejected with reason)
- Conflict marker scan results
- Export guard trigger events (empty DB, stale DB)
- Atomic write operations (temp file creation, rename)
If a safety guard triggers unexpectedly, the verbose log will show exactly why.
Even if br sync has a bug, it cannot delete your source code.
This is not a best-effort promise—it's an architectural constraint enforced by:
- Code that literally cannot call git (no git library, no shell-out to git)
- Path validation that rejects anything outside
.beads/ - Tests that would fail if these constraints were violated
For technical details, see:
.beads/SYNC_THREAT_MODEL.md- Incident analysis and failure scenarios.beads/SYNC_SAFETY_INVARIANTS.md- Testable safety invariants.beads/SYNC_CLI_FLAG_SEMANTICS.md- Flag matrix and opt-in rules
This document is part of the br safety hardening initiative.