feat(sync): serialize sync.sh with a per-machine lock; per-session log filenames

Multiple concurrent Claude sessions (and the scheduled-task sync) were
stepping on each other's git state. sync.sh now takes an atomic mkdir
lock in .git/ around the whole run (stage/commit/fetch/rebase/push +
vault), exits 75 (EX_TEMPFAIL = deferred) on contention instead of
racing, and reclaims stale/dead-owner locks with a re-verify-before-clear
guard (closes two TOCTOU races caught in review). /save now mandates
per-session-unique log filenames (never the bare YYYY-MM-DD-session.md).
Docs updated for the lock + deferred-exit semantics.

Note: git add -A is still the catch-all sweep; full per-session commit
isolation and routing /scc + /checkpoint through the lock are follow-ups.
This commit is contained in:
2026-06-05 18:50:15 -07:00
parent 6cc835f0fe
commit a85c2cc9e2
3 changed files with 23 additions and 12 deletions

View File

@@ -96,7 +96,7 @@ The article + `wiki/index.md` are committed alongside the session log by `sync.s
bash .claude/scripts/sync.sh
```
`sync.sh` is **serialized by a per-machine lock** (`.git/claudetools-sync.lock`) so concurrent sessions or the scheduled-task sync cannot interleave commits/rebases; if another sync is mid-flight it waits up to ~120s, then skips (the next sync catches up). It then handles: reconcile this machine's `git config user.name/email` to `.claude/identity.json` (so commit authorship can't drift), stage all changes with `git add -A` (after purging garbled Windows path-as-filename cruft), auto-commit, fetch + rebase, push, then the same flow for the vault repo, then surface cross-user `## Note for <user>` blocks.
`sync.sh` is **serialized by a per-machine lock** (`.git/claudetools-sync.lock`) so concurrent sessions or the scheduled-task sync cannot interleave commits/rebases; if another sync is mid-flight it waits up to ~120s, then **exits 75 (deferred)** rather than racing — the next sync catches up. On a 75, do NOT print a success summary; report "**sync deferred — another sync is running; your session log is written locally and will sync on the next run**". Otherwise it: reconciles this machine's `git config user.name/email` to `.claude/identity.json` (so commit authorship can't drift), stages all changes with `git add -A` (after purging garbled Windows path-as-filename cruft), auto-commits, fetch + rebase, push, then the same flow for the vault repo, then surfaces cross-user `## Note for <user>` blocks.
> Note: `git add -A` is still the catch-all sweep, so a save run will also pick up any *other* dirty files in the shared tree. The lock prevents two syncs from racing, and per-session-unique log filenames prevent log overwrites — but the bare-`add -A` capture means full per-session commit isolation is a later step (see the isolation plan: drop blind `add -A` in favour of explicit per-session staging). For now, avoid running `/save` from two sessions at the exact same moment.