fix(daemon): add sync-branch guard to daemon code paths (#1271)
* fix(daemon): skip export when sync-branch matches current Prevent redundant export operations by checking if the daemon's sync branch matches the current active branch. Previously, the daemon would attempt to perform an export even when already on the target branch. This logic now skips the export step in such cases to avoid unnecessary overhead and potential conflicts. Includes a new integration test to verify the guard logic. * fix(daemon): prevent sync on guarded branches Add checks to verify if a branch is guarded before performing automated sync cycles, auto-imports, or branch-specific commit and pull operations. This prevents the daemon from modifying protected branches or running synchronization tasks where they are restricted. Includes comprehensive integration tests to verify the guard logic during sync-branch operations. * fix(daemon): warn on sync branch misconfiguration at startup The daemon now checks for sync branch name conflicts during its startup loop. This provides early feedback if the sync branch is configured in a way that might conflict with existing branches or other settings. The warnIfSyncBranchMisconfigured function performs the validation and logs a warning to the console. Integration tests verify that the daemon correctly identifies and reports these misconfigurations at initialization.
This commit is contained in:
committed by
GitHub
parent
b7d650bd8e
commit
dbd505656d
@@ -529,6 +529,13 @@ func runDaemonLoop(interval time.Duration, autoCommit, autoPush, autoPull, local
|
||||
log.Warn("repository mismatch ignored (BEADS_IGNORE_REPO_MISMATCH=1)")
|
||||
}
|
||||
|
||||
// GH#1258: Warn at startup if sync-branch == current-branch (misconfiguration)
|
||||
// This is a one-time warning - per-operation skipping is handled by shouldSkipDueToSameBranch()
|
||||
// Skip check in local mode (no sync-branch is used)
|
||||
if !localMode {
|
||||
warnIfSyncBranchMisconfigured(ctx, store, log)
|
||||
}
|
||||
|
||||
// Validate schema version matches daemon version
|
||||
versionCtx := context.Background()
|
||||
dbVersion, err := store.GetMetadata(versionCtx, "bd_version")
|
||||
|
||||
@@ -16,9 +16,48 @@ import (
|
||||
"github.com/steveyegge/beads/internal/config"
|
||||
"github.com/steveyegge/beads/internal/storage"
|
||||
"github.com/steveyegge/beads/internal/storage/sqlite"
|
||||
"github.com/steveyegge/beads/internal/syncbranch"
|
||||
"github.com/steveyegge/beads/internal/types"
|
||||
)
|
||||
|
||||
// warnIfSyncBranchMisconfigured logs a warning at daemon startup if sync-branch
|
||||
// equals the current branch. This is a one-time warning to alert users about
|
||||
// the misconfiguration. The daemon continues to start (warn only, don't block).
|
||||
// Returns true if misconfigured (warning was logged), false otherwise.
|
||||
// GH#1258: Prevents silent failure when sync-branch == current-branch.
|
||||
func warnIfSyncBranchMisconfigured(ctx context.Context, store storage.Storage, log daemonLogger) bool {
|
||||
syncBranch, err := syncbranch.Get(ctx, store)
|
||||
if err != nil || syncBranch == "" {
|
||||
return false // No sync branch configured, not misconfigured
|
||||
}
|
||||
|
||||
if syncbranch.IsSyncBranchSameAsCurrent(ctx, syncBranch) {
|
||||
log.Warn("sync-branch misconfiguration detected",
|
||||
"sync_branch", syncBranch,
|
||||
"message", "sync-branch is your current branch; daemon sync operations will be skipped; configure a dedicated sync branch (e.g., 'beads-sync') to enable sync")
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// shouldSkipDueToSameBranch checks if operation should be skipped because
|
||||
// sync-branch == current-branch. Returns true if should skip, logs reason.
|
||||
// Uses fail-open pattern: if branch detection fails, allows operation to proceed.
|
||||
func shouldSkipDueToSameBranch(ctx context.Context, store storage.Storage, operation string, log daemonLogger) bool {
|
||||
syncBranch, err := syncbranch.Get(ctx, store)
|
||||
if err != nil || syncBranch == "" {
|
||||
return false // No sync branch configured, allow
|
||||
}
|
||||
|
||||
if syncbranch.IsSyncBranchSameAsCurrent(ctx, syncBranch) {
|
||||
log.log("Skipping %s: sync-branch '%s' is your current branch. Use a dedicated sync branch.", operation, syncBranch)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// exportToJSONLWithStore exports issues to JSONL using the provided store.
|
||||
// If multi-repo mode is configured, routes issues to their respective JSONL files.
|
||||
// Otherwise, exports to a single JSONL file.
|
||||
@@ -437,6 +476,13 @@ func performExport(ctx context.Context, store storage.Storage, autoCommit, autoP
|
||||
if skipGit {
|
||||
mode = "local export"
|
||||
}
|
||||
|
||||
// Guard: Skip if sync-branch == current-branch (GH#1258)
|
||||
// Local-only mode (skipGit) doesn't use sync-branch, so skip the guard
|
||||
if !skipGit && shouldSkipDueToSameBranch(exportCtx, store, mode, log) {
|
||||
return
|
||||
}
|
||||
|
||||
log.log("Starting %s...", mode)
|
||||
|
||||
jsonlPath := findJSONLPath()
|
||||
@@ -587,6 +633,12 @@ func performAutoImport(ctx context.Context, store storage.Storage, skipGit bool,
|
||||
mode = "local auto-import"
|
||||
}
|
||||
|
||||
// Guard: Skip if sync-branch == current-branch (GH#1258)
|
||||
// Local-only mode (skipGit) doesn't use sync-branch, so skip the guard
|
||||
if !skipGit && shouldSkipDueToSameBranch(importCtx, store, mode, log) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check backoff before attempting sync (skip for local mode)
|
||||
if !skipGit {
|
||||
jsonlPath := findJSONLPath()
|
||||
@@ -725,6 +777,13 @@ func performSync(ctx context.Context, store storage.Storage, autoCommit, autoPus
|
||||
if skipGit {
|
||||
mode = "local sync cycle"
|
||||
}
|
||||
|
||||
// Guard: Skip if sync-branch == current-branch (GH#1258)
|
||||
// Local-only mode (skipGit) doesn't use sync-branch, so skip the guard
|
||||
if !skipGit && shouldSkipDueToSameBranch(syncCtx, store, mode, log) {
|
||||
return
|
||||
}
|
||||
|
||||
log.log("Starting %s...", mode)
|
||||
|
||||
jsonlPath := findJSONLPath()
|
||||
|
||||
@@ -31,7 +31,12 @@ func syncBranchCommitAndPushWithOptions(ctx context.Context, store storage.Stora
|
||||
if !hasGitRemote(ctx) {
|
||||
return true, nil // Skip sync branch commit/push in local-only mode
|
||||
}
|
||||
|
||||
|
||||
// Guard: Skip if sync-branch == current-branch (GH#1258)
|
||||
if shouldSkipDueToSameBranch(ctx, store, "sync-branch commit", log) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Get sync branch configuration (supports BEADS_SYNC_BRANCH override)
|
||||
syncBranch, err := syncbranch.Get(ctx, store)
|
||||
if err != nil {
|
||||
@@ -252,7 +257,12 @@ func syncBranchPull(ctx context.Context, store storage.Storage, log daemonLogger
|
||||
if !hasGitRemote(ctx) {
|
||||
return true, nil // Skip sync branch pull in local-only mode
|
||||
}
|
||||
|
||||
|
||||
// Guard: Skip if sync-branch == current-branch (GH#1258)
|
||||
if shouldSkipDueToSameBranch(ctx, store, "sync-branch pull", log) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Get sync branch configuration (supports BEADS_SYNC_BRANCH override)
|
||||
syncBranch, err := syncbranch.Get(ctx, store)
|
||||
if err != nil {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user