Feature request: built-in git-worktree auto-sync for --spawn worktree multi-session workflows (working hook pattern included)
Description:
Running multiple concurrent sessions via --spawn worktree (or git worktree add directly), each session commits to its own branch in isolation. Nothing native keeps those branches synced with the main checkout, so fully-committed work can sit practically invisible — not visible to other sessions, to a human browsing the repo, or to anything served from the main checkout — until someone manually merges it. We hit this repeatedly running several long-lived worktree sessions against one shared repo: real work sat unmerged for hours to days more than once before anyone noticed.
What we built to work around it (holding up well over a week of real use):
A shared post-commit/post-merge hook (needs both — a real non-fast-forward merge fires post-merge, not post-commit) that:
- After every commit in a worktree, tries a fast-forward-only merge of that branch into the main checkout. Fast-forward only, never forces or resolves a real conflict itself.
- If that's blocked only because the main checkout moved ahead in the meantime (not a real conflict), pulls the main checkout in first, then retries the forward — still fully automatic, still conflict-free by construction.
- If neither succeeds, it's a genuine divergence. Instead of a generic "needs manual merge," we run a read-only
git merge-tree --write-tree --name-onlyto report exactly which files actually conflict, versus it just being a mechanically-blocked-but-content-clean gap. - Writes a single, idempotent pending-merge notice (keyed to branch name, so concurrent worktrees don't clobber each other's notices) somewhere visible, so anyone can immediately see what's diverged and how serious it is.
One sharp, non-obvious bug worth flagging for anyone attempting this themselves: if that notice is left uncommitted in the main checkout's working tree, it silently blocks the next legitimate fast-forward touching the same file — the status mechanism becomes its own blocker. Fix: commit the notice immediately, never leave it sitting uncommitted.
Suggested feature:
Since --spawn worktree is already first-class, an opt-in "auto-forward my worktree toward the main checkout, fast-forward only, tell me clearly when it can't" mode feels like it belongs natively rather than everyone hand-rolling this. Happy to share the actual hook script — fully working, just needs genericizing from our own repo layout.
6 Comments
As promised — here's a working reference implementation of the pattern described above: https://github.com/mwfoutch/claude-worktree-autosync
post-commit/post-mergehooks that auto-forward a worktree branch into a main checkout (fast-forward only), with a read-onlygit merge-treecheck to tell you exactly which files conflict versus it just being a mechanical non-fast-forward gap. MIT licensed, install script included. Happy to answer questions if this is useful to anyone else hitting the same problem.The worktree sync problem you're describing is real and your hook approach is solid. The fast-forward-only constraint is the right call -- it means the hook never silently resolves a real conflict, which is the failure mode that would make this dangerous to run unattended.
One thing worth adding to the hook if you haven't already: a lockfile or semaphore around the merge-and-retry step. In setups with 4+ concurrent worktree sessions all committing at roughly the same time, you can get multiple hooks racing each other on the same main checkout, and even with fast-forward-only, concurrent fast-forward attempts can corrupt the ref. A flock on a sentinel file in the repo root keeps them serialized.
The broader feature request makes sense. A
--spawnsession that provisions a worktree but leaves sync entirely manual is only halfway there. The useful primitive is "spawn an isolated branch, do work, and have completed work surface to the main checkout automatically without human-mediated merges." Your hook gets most of the way there; the missing piece is first-class visibility into which spawned sessions have pending unmerged commits, so you don't have to go looking.Curious how you're handling conflicts that can't fast-forward -- are those surfaced to the user immediately (blocking the post-commit hook) or queued for manual review?
Good questions, thanks for digging in.
On the fast-forward-blocked case: it's queued for manual review, not surfaced as a blocking error -- the hook never interrupts the commit itself either way. When it can't fast-forward, it runs a read-only
git merge-treefirst to tell a genuine content conflict apart from a merely-blocked-but-clean divergence (different problems, different message), then writes a single idempotent notice into a status file. Discovery is passive right now -- someone has to open that file -- not pushed to anyone in real time. That's an honest limitation, not a design choice I'd defend as ideal.On the lock/semaphore: you're right that it's missing from the merge-and-retry step itself. I do use
flockelsewhere in my own hook chain, guarding a couple of background jobs so a burst of rapid commits doesn't pile up overlapping runs -- just not around this specific sequence. One nuance worth being precise about: git's own ref updates are individually atomic, so I don't think concurrent fast-forwards literally corrupt a ref -- but you're right that concurrent runs of the script's own multi-step logic (check ff-only, maybe pull-and-retry, maybe report divergence) can still race in confusing ways once you're at 4+ concurrent sessions, since each invocation's assumptions can go stale mid-sequence. That's a real gap, not a hypothetical one.On visibility: the status file is meant to be exactly that -- one place aggregating every pending divergence instead of checking each worktree by hand. It's still passive today, though. A real next step would be a notification on top of it, not just the file itself.
Wanted to follow up and actually close the loop on this, since your two suggestions turned into real changes rather than just discussion.
On the lock/semaphore: added it. The worktree-forward block is now wrapped in a
flockwith a 30-second timeout -- blocking, not-n/skip, since silently dropping a real forward attempt would defeat the whole point. If the lock can't be acquired in time it falls through with a logged warning instead of hanging the commit indefinitely.On the visibility point: added a push notification meant to fire the moment a divergence is detected. Real story on how that went: the very first real commit after shipping the lock caught a genuine divergence from concurrent work on the spot -- the lock/detection half worked exactly as intended. The notification didn't fire for it, though. Checked, not assumed. Turned out to be a real bug: a banner for that branch already existed from earlier the same day, and the dedup check that stops duplicate banners only asks "does one already exist for this branch," not "does it still match the current situation" -- so it silently suppressed the new notification too, since that call sat behind the same check.
Fixed now: moved the notification outside the dedup check entirely, so it fires on every real divergence, while the banner text itself stays deduped separately. Reproduced the exact scenario in an isolated repo before trusting it -- confirmed the notification fires on a second, escalating commit even when a banner already exists, and the banner still doesn't get duplicated.
Pushed both fixes (lock + corrected notification) to the reference repo too, so anyone pulling it now gets the version that's actually been through this. Appreciate you pushing on both points -- the second one turned into a better bug hunt than I expected.
Not EXACTLY what your asking but https://norrin.dev/ is a very cool extension for Claude Code that gives you the ability to see inline diff, accept/ reject per hunk, and track files.
Thanks for the pointer — norrin.dev's inline diff / accept-reject-per-hunk / file-tracking feature set looks like it's solving a different problem than this issue (reviewing a single session's edits vs. syncing work across spawned worktree sessions), but it's a real gap worth having a good answer for too. Haven't tried it myself, so no endorsement either way — will take a look.