From 97500a8de0d234c4e222f268b42b7d0aaf396a76 Mon Sep 17 00:00:00 2001 From: Steve Yegge Date: Sat, 13 Dec 2025 06:45:13 -0800 Subject: [PATCH] fix(sync): prevent daemon race condition from dirtying working directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After bd sync completes with sync.branch mode, the daemon or next CLI command could see a hash mismatch between the restored JSONL file and the DB metadata, triggering auto-import which then schedules re-export, dirtying the working directory. Two fixes: 1. sync.go: Update jsonl_content_hash after restoreBeadsDirFromBranch to match the restored file hash 2. daemon_sync.go: Update jsonl_content_hash after performAutoImport succeeds (was missing, unlike CLI import path) Fixes: bd-lw0x, bd-hxou 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- cmd/bd/daemon_sync.go | 12 ++++++++++++ cmd/bd/sync.go | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/cmd/bd/daemon_sync.go b/cmd/bd/daemon_sync.go index cf5b7a7c..9e3155d2 100644 --- a/cmd/bd/daemon_sync.go +++ b/cmd/bd/daemon_sync.go @@ -598,6 +598,18 @@ func performAutoImport(ctx context.Context, store storage.Storage, skipGit bool, return } + // Update jsonl_content_hash after successful import to prevent repeated imports + // Uses repoKey for multi-repo support (bd-ar2.10, bd-ar2.11) + hashKey := "jsonl_content_hash" + if repoKey != "" { + hashKey += ":" + repoKey + } + if currentHash, err := computeJSONLHash(jsonlPath); err == nil { + if err := store.SetMetadata(importCtx, hashKey, currentHash); err != nil { + log.log("Warning: failed to update %s after import: %v", hashKey, err) + } + } + if skipGit { log.log("Local auto-import complete") } else { diff --git a/cmd/bd/sync.go b/cmd/bd/sync.go index ec370b07..ec03f150 100644 --- a/cmd/bd/sync.go +++ b/cmd/bd/sync.go @@ -666,6 +666,15 @@ Use --merge to merge the sync branch back to main branch.`, if err := restoreBeadsDirFromBranch(ctx); err != nil { // Non-fatal - just means git status will show modified files debug.Logf("sync: failed to restore .beads/ from branch: %v", err) + } else { + // Update jsonl_content_hash to match the restored file + // This prevents daemon/CLI from seeing a hash mismatch and re-importing + // which would trigger re-export and dirty the working directory (bd-c83r race fix) + if restoredHash, err := computeJSONLHash(jsonlPath); err == nil { + if err := store.SetMetadata(ctx, "jsonl_content_hash", restoredHash); err != nil { + debug.Logf("sync: failed to update hash after restore: %v", err) + } + } } // Skip final flush in PersistentPostRun - we've already exported to sync branch // and restored the working directory to match the current branch