Skip to content

Repository files navigation

syncgit

One command to sync parallel Claude Code worktrees.

Type /sync. Your work goes out, their work comes in, history stays clean.


Why

Running three Claude agents in three worktrees sounds great until you try to merge their work. Somebody has to be main. Somebody has to rebase. Somebody has to decide whose branch wins. You spend more time shepherding git than shipping code.

/sync is a Claude Code slash command that does the whole dance for you — review the diff, commit, rebase in every peer's work, verify, broadcast. One keystroke from any worktree. No central branch, no merge queue, no human in the loop.

What /sync looks like

Three agents on a project — backend, frontend, docs. Each /sync absorbs whatever peers have broadcast, then pushes its own work back out. Order doesn't matter; the history converges.

Each /sync broadcasts to every other peer — one action, an arrowhead at each recipient.

sequenceDiagram
  participant a1 as agent1 (backend)
  participant a2 as agent2 (frontend)
  participant a3 as agent3 (docs)

  Note over a1: updates backend
  Note over a2: updates frontend

  a1->>a2: /sync — backend
  a1->>a3: 
  Note over a2: absorbs backend
  a2->>a1: /sync — frontend
  a2->>a3: 
  Note over a3: absorbs backend + frontend
  a3->>a1: /sync (catches up)
  a3->>a2: 
  Note over a1: absorbs frontend
  a1->>a2: /sync
  a1->>a3: 

  Note over a1: more backend edits
  Note over a2: wires frontend to backend
  Note over a3: updates docs

  a1->>a2: /sync — backend v2
  a1->>a3: 
  Note over a2: absorbs backend v2
  a2->>a1: /sync — wiring
  a2->>a3: 
  Note over a3: absorbs backend v2 + wiring
  a3->>a1: /sync — docs
  a3->>a2: 
Loading

By the end every worktree has the same linear history: backend → frontend → backend v2 → wiring → docs. Nobody had to be main.

Two modes: /sync and /ssync

syncgit ships two sync commands; pick one per situation.

  • /sync — the divergence-tolerant broadcast mode described above. Any peer can broadcast at any time without first absorbing the others; syncgit folds divergent peer tips together with a merge-commit chain that preserves every peer's commit SHA. Use it when peers are genuinely working in parallel and you don't want to coordinate ordering.
  • /ssync — a simpler serial mode. All peers share one moving pointer, refs/syncgit/mainline. A sync rebases your work onto the latest tip and advances the pointer with an atomic compare-and-swap; if a peer advanced it first, you rebase onto the new tip and retry. Because syncs are effectively serialized, history is always a clean straight line — no merge commits, no divergence handling. Use it for the common case where peers sync roughly one at a time.

The two modes publish through different git objects (refs/pr/* vs refs/syncgit/mainline), so they coexist safely — but they do not observe each other's published work. A given sync round should use one mode team-wide; don't mix /sync and /ssync in the same round. syncgit smerge warns if peers have outstanding /sync broadcasts so you notice before drifting. See docs/architecture.md for the mechanics.

Migrating a running /sync project to /ssync

refs/syncgit/mainline starts at the seed commit, so the broadcast world must be flushed onto the mainline before everyone switches — otherwise the first /ssync would rebase onto the seed and orphan work. Two ways to do it:

Automatic, per agent (recommended). Run /sync2ssync once on each agent after it finishes its current work — order between agents doesn't matter. Each run does one final /sync (to flush anything still in refs/pr/* into its HEAD) then one /ssync (to land that HEAD on the mainline). The first agent to reach the /ssync step adopts the mainline automatically — its spush compare-and-swaps the pointer up from the seed — and later agents rebase onto it and advance it; lost compare-and-swaps just retry. No central coordinator. The team is half-migrated until every agent has run it (expected); each agent becomes consistent the moment it finishes. When all are done, change CLAUDE.md to instruct /ssync.

Manual, one operator command. If you'd rather do it by hand: (1) have every agent finish its /sync and clear any in-flight merge; (2) run /sync (merge + push) rounds until all peers share one HEAD and the refs/pr/* queue is empty; (3) from any converged worktree run syncgit mainline adopt (it points the mainline at that tip and refuses if you still have unabsorbed PRs or an in-flight merge); (4) switch CLAUDE.md to /ssync.

The reverse (going back to /sync) needs no special step — just have the team resume /sync; the refs/pr/* broadcast queue works from wherever each peer's HEAD is.

Quick start

# install
git clone https://github.com/trumanellis/syncgit ~/Code/syncgit
cd ~/Code/syncgit && ./install.sh

The installer drops /sync, /ssync, and /sync2ssync into ~/.claude/commands/ so every Claude Code session can use them.

# set up a project
cd ~/Code/myproj
syncgit init --peers agent1 agent2 agent3
# one terminal per peer
cd ~/Code/myproj/agent1 && claude
cd ~/Code/myproj/agent2 && claude
cd ~/Code/myproj/agent3 && claude

Give each agent different work. When one finishes, it types /sync (or /ssync for the serial single-mainline mode — see Two modes).

Project setup

Drop CLAUDE.md.example into your project's CLAUDE.md so each agent knows to use /sync instead of committing manually.

Optional per-repo config inside any worktree:

  • .syncgit/ignore — extra paths never to stage
  • .syncgit/verify.sh (executable) — gate broadcasts on a build/test

What happens under the hood

Each worktree adds every sibling as a local git remote. The "PR queue" between peers is just git refs (refs/pr/<peer>/<timestamp>). Worktrees share a ref database, so a push to one peer is instantly visible to every other peer — no daemon, no server, no central repo. /sync is a thin orchestrator over the syncgit CLI that wraps the whole loop.

If a rebase can't resolve cleanly after 3 tries, the agent halts and writes .syncgit/last-halt.md rather than guessing.

Subcommands

Command Purpose
syncgit init --peers a,b,c Create parent repo and N worktrees, wire remotes
syncgit peers list|add|remove Manage peer set (add/remove work live)
syncgit status Show inbound/outbound PR queue
syncgit fetch Fetch refs/pr/* from every peer (seam for network transport)
syncgit stage Show categorized diff for agent review
syncgit merge Absorb pending peer PRs
syncgit verify Run .syncgit/verify.sh if present
syncgit push Broadcast HEAD to peers and run GC
syncgit smerge (/ssync) Rebase onto the shared serial mainline
syncgit spush (/ssync) Advance the serial mainline via compare-and-swap
syncgit mainline [show|adopt] (/ssync) Show the mainline, or adopt HEAD (sync→ssync migration)
syncgit gc Garbage-collect absorbed and TTL-expired PR refs
syncgit show <ref> Show log and diffstat of <ref> relative to HEAD
syncgit abort Roll back to pre-merge or pre-squash snapshot
syncgit squash Collapse self-authored commits since last push into one
syncgit unlock Remove a stale .syncgit/lock left by a crashed agent

Global flags: -q/--quiet, -v/--verbose, --version/-V, -h/--help. Every subcommand also accepts -h/--help.

Requirements

  • Bash ≥ 3.2 (macOS default works)
  • Git ≥ 2.23
  • Python 3 ≥ 3.6 (date math and JSON parsing)
  • macOS or Linux (Windows likely works under Git Bash; untested)

Configuration

Environment variables:

Variable Default Description
SYNCGIT_MERGE_STRATEGY merge merge preserves all peer SHAs via merge-commit chain. rebase gives strictly linear history; only the last peer's SHA survives. See docs/architecture.md.
SYNCGIT_TTL_DAYS 14 Refs older than this (days) are dropped during gc / push.
SYNCGIT_VERBOSITY normal quiet / normal / verbose. Equivalent to the -q / -v global flags.

How merging works

When you run syncgit merge, it absorbs every pending peer PR in chronological order. By default (merge strategy), it chains the peer refs together with merge commits, so every peer's original commit SHA remains reachable in your history—critical for GC to detect absorption. If you prefer strict linear history, set SYNCGIT_MERGE_STRATEGY=rebase to get the legacy behavior, though only the last peer's SHA survives the rebase intact.

Squashing your own commits

syncgit squash collapses all of your commits made since your last syncgit push into a single commit. It only touches commits you authored — peer commits and merge commits in the same range cause it to refuse with a clear message (push first to broadcast, then squash on the next round).

To identify which commits are "yours", syncgit init sets a per-worktree git identity (git config user.name <peer-id>) on each worktree it creates. This is a local, worktree-scoped config — it does not touch your global ~/.gitconfig. If you ever need to re-initialize the identity (e.g. on a worktree created before this feature), run git config user.name <your-peer-id> inside the worktree, or re-run syncgit init to have it applied automatically.

Exit codes

Code Meaning
0 Success
1 User error or partial broadcast (see stderr)
2 Halted — see .syncgit/last-halt.md for details
3 Merge or rebase conflict — see the error message for the resolution path

Troubleshooting

Lock stuck after a crash. Run syncgit unlock from inside the worktree. The lock is a directory at .syncgit/lock; unlock removes it unconditionally.

"previous merge in progress". A previous syncgit merge left a snapshot (refs/syncgit/pre-merge). Either run syncgit push to broadcast the merged state, or syncgit abort to roll back to the pre-merge state.

Chain-merge conflict. When two peer branches edit the same lines, the default merge strategy auto-rolls-back to your original branch and prints recovery instructions. Retry with SYNCGIT_MERGE_STRATEGY=rebase syncgit merge to surface per-peer conflicts one at a time. Resolve each with git add <files> && git rebase --continue. syncgit abort is available to roll back at any point.

"squash refuses with mixed range". The commit range contains peer commits or merge commits. Run syncgit push first to broadcast, then squash on the next round when the range is clean self-authored commits only.

Disjoint history error. A peer was bootstrapped from a different seed commit and shares no common ancestor with HEAD. Inspect the offending peer ref with syncgit show <ref> and remove it manually if it is not legitimate.

Testing and CI

Run the test suite locally:

bash tests/run.sh

The suite runs 14 black-box scenarios covering init, peers, merge strategies, GC, abort, squash, unlock, the /ssync serial mainline (sequential, compare-and-swap contention, conflict + abort), and the /sync/ssync migration path.

CI runs on every push and pull request via GitHub Actions:

  • Matrix: ubuntu-latest and macos-latest
  • shellcheck lints all shell scripts
  • Full test suite runs on both platforms

OS coverage

OS Status
macOS 14+ tested
Ubuntu 22.04+ tested via CI
Windows untested (Git Bash should work)

Documentation

  • docs/architecture.md — peer SHA preservation, merge-commit chain design, and the /ssync serial mainline model
  • docs/ssync-explained.html — interactive, step-through visualization of the /ssync smerge→spush cycle, compare-and-swap contention, and conflict/abort (open in a browser)
  • docs/security.md — trust model, attack surface, verify.sh guidance
  • CHANGELOG.md — release history
  • CONTRIBUTING.md — development setup, commit style, releasing
  • examples/ — sample .syncgit/verify.sh and .syncgit/ignore

Not married to Claude Code

/sync is the Claude Code frontend, but syncgit itself is a CLI plus a git ref convention — any agent or human can drive the same loop. See bin/syncgit for the underlying commands.

Teardown

cd ~/Code/myproj
for p in agent1 agent2 agent3; do git worktree remove "$p"; done
git branch -D agent1 agent2 agent3
rm -rf .syncgit

Stewardship

syncgit is stewarded by templesofrefuge.earth and is part of the syncengine.earth project — an expression of the same flat-protocol, no-hub ethos applied to code coordination.

License

MIT © Truman Ellis

Many hands. One tree. No hub.

About

No description, website, or topics provided.

Resources

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages