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

@@ -50,6 +50,8 @@ Invokes `bash .claude/scripts/sync.sh`, which:
The script is the single source of truth for git operations. Both `/sync` and `/save` invoke it.
**Concurrency:** the run is serialized by a per-machine lock (`.git/claudetools-sync.lock`) so two syncs (e.g. interactive + the scheduled-task sync, or two Claude sessions) can't interleave staging/commit/rebase/push. If another sync is already running, this run waits up to ~120s then **exits 75 (EX_TEMPFAIL = deferred, not a failure)** — report it as deferred, not synced; the next run catches up. Stale locks (owner process dead, or older than 10 min) are auto-reclaimed.
---
## Cross-user note handling (CRITICAL)