{"record":{"id":"3742b2dcb974b62d","repo":"gastownhall/beads","slug":"merge-conflicts-require-resolution-use-strategy","errorCode":null,"errorMessage":"merge conflicts require resolution (use --strategy ours|theirs)","messagePattern":"merge conflicts require resolution \\(use --strategy ours\\|theirs\\)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/dolt/federation.go","lineNumber":387,"sourceCode":"\n\t// Step 2: Get status before merge\n\tbeforeCommit, _ := s.GetCurrentCommit(ctx) // Best effort: empty commit hash means diff won't be logged\n\n\t// Step 3: Merge peer's branch\n\tremoteBranch := fmt.Sprintf(\"%s/%s\", peer, s.branch)\n\tconflicts, err := s.Merge(ctx, remoteBranch)\n\tif err != nil {\n\t\tresult.Error = fmt.Errorf(\"merge failed: %w\", err)\n\t\treturn result, result.Error\n\t}\n\n\t// Step 4: Handle conflicts if any\n\tif len(conflicts) > 0 {\n\t\tresult.Conflicts = conflicts\n\n\t\tif strategy == \"\" {\n\t\t\t// No strategy specified, leave conflicts for manual resolution\n\t\t\tresult.Error = fmt.Errorf(\"merge conflicts require resolution (use --strategy ours|theirs)\")\n\t\t\treturn result, result.Error\n\t\t}\n\n\t\t// Auto-resolve using strategy\n\t\tfor _, c := range conflicts {\n\t\t\tif err := s.ResolveConflicts(ctx, c.Field, strategy); err != nil {\n\t\t\t\tresult.Error = fmt.Errorf(\"conflict resolution failed for %s: %w\", c.Field, err)\n\t\t\t\treturn result, result.Error\n\t\t\t}\n\t\t}\n\t\tresult.ConflictsResolved = true\n\n\t\t// Commit the resolution INCLUDING config: the operator chose this\n\t\t// strategy, and plain Commit excludes config (GH#2455). A config-only\n\t\t// conflict — routine now that kv.memory.* memories sync through config —\n\t\t// would otherwise resolve but never commit, leaving the merge\n\t\t// unconcluded and re-wedging the next sync.\n\t\tif err := s.CommitMergeResolution(ctx, fmt.Sprintf(\"Resolve conflicts from %s using %s strategy\", peer, strategy)); err != nil {","sourceCodeStart":369,"sourceCodeEnd":405,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/dolt/federation.go#L369-L405","documentation":"Sync detected merge conflicts but was called without a resolution strategy, so it intentionally stops and leaves conflicts for manual resolution rather than guessing. The conflicts themselves are attached to result.Conflicts. This is a deliberate control-flow error telling the operator to re-run with --strategy ours|theirs or resolve by hand.","triggerScenarios":"store.Sync(ctx, peer, \"\" /* empty strategy */, opts) where Step 3's Merge returns a non-empty conflict list.","commonSituations":"Automated sync jobs that never pass a strategy hitting divergent edits to the same issue rows; two operators editing the same issue on different replicas; first-time federation setup where both sides created overlapping config/kv.memory entries.","solutions":["Re-run sync with an explicit strategy: ours keeps local rows, theirs takes peer rows","Inspect result.Conflicts to see which fields conflict before choosing a strategy","Resolve conflicts manually (ResolveConflicts per field) then commit and re-sync","Prevent divergence by syncing more frequently so fewer concurrent edits accumulate","Decide a house policy (e.g. last-writer-wins via 'theirs') for automated pipelines"],"exampleFix":"// before\nresult, err := store.Sync(ctx, \"origin\", \"\", nil)\n// after\nresult, err := store.Sync(ctx, \"origin\", \"theirs\", nil)\nif result != nil && len(result.Conflicts) > 0 {\n    log.Printf(\"auto-resolved %d conflicts using theirs\", len(result.Conflicts))\n}","handlingStrategy":"fallback","validationCode":"if strategy != \"ours\" && strategy != \"theirs\" && strategy != \"\" {\n    return fmt.Errorf(\"invalid strategy %q\", strategy)\n}","typeGuard":"func isUnresolvedConflictsErr(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"require resolution\")\n}","tryCatchPattern":"result, err := store.Sync(ctx, peer, strategy, opts)\nif isUnresolvedConflictsErr(err) && len(result.Conflicts) > 0 {\n    // retry with a chosen strategy\n    result, err = store.Sync(ctx, peer, \"theirs\", opts)\n}","preventionTips":["Always pass an explicit strategy in automated/CI sync paths","Inspect result.Conflicts to pick ours vs theirs deliberately","Sync often to minimize conflicting concurrent edits"],"tags":["dolt","federation","sync","conflict"],"backgroundTag":"merge-conflicts-unresolved","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}