gastownhall/beads · error

conflict resolution failed for %s: %w

Error message

conflict resolution failed for %s: %w

What it means

After conflicts are detected and a strategy was given, Sync resolves each conflicting field via s.ResolveConflicts(ctx, c.Field, strategy). If resolution of any field fails, the error is wrapped with the offending field name, aborting the sync with the merge still in a conflicted state.

Source

Thrown at internal/storage/embeddeddolt/federation.go:338

	remoteBranch := fmt.Sprintf("%s/%s", peer, s.branch)
	conflicts, err := s.Merge(ctx, remoteBranch)
	if err != nil {
		result.Error = fmt.Errorf("merge failed: %w", err)
		return result, result.Error
	}

	// Step 4: Handle conflicts
	if len(conflicts) > 0 {
		result.Conflicts = conflicts

		if strategy == "" {
			result.Error = fmt.Errorf("merge conflicts require resolution (use --strategy ours|theirs)")
			return result, result.Error
		}

		for _, c := range conflicts {
			if err := s.ResolveConflicts(ctx, c.Field, strategy); err != nil {
				result.Error = fmt.Errorf("conflict resolution failed for %s: %w", c.Field, err)
				return result, result.Error
			}
		}
		result.ConflictsResolved = true

		// CommitMergeResolution, not Commit: Commit's GH#3886 nothing-to-commit
		// tolerance would swallow the --ours case (resolution dirties nothing)
		// as a silent no-op here, leaving dolt_merge_status.is_merging true while
		// this function reports result.Merged = true and pushes — the exact
		// re-wedge CommitMergeResolution's doc comment describes. See the
		// server-mode twin, dolt/federation.go's Sync.
		if err := s.CommitMergeResolution(ctx, fmt.Sprintf("Resolve conflicts from %s using %s strategy", peer, strategy)); err != nil {
			result.Error = fmt.Errorf("commit conflict resolution: %w", err)
			return result, result.Error
		}

		// bd-578h9.11: the conflicted merge skipped the automatic is_blocked
		// recompute (unresolved rows would have fed it garbage); now that the

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the named field in the error — verify both peers have compatible schemas for it.
  2. Re-run sync with the strategy; resolution is retryable per field.
  3. Inspect dolt conflicts tables to see the row-level conflict and resolve manually if needed.
  4. Abort the in-progress merge (dolt merge --abort equivalent) and re-sync from a clean state.

Example fix

// before: unknown field fails resolution
for _, c := range conflicts {
    if err := s.ResolveConflicts(ctx, c.Field, strategy); err != nil { ... }
}
// after: skip/log fields the resolver cannot handle, resolve the rest
for _, c := range conflicts {
    if !isResolvableField(c.Field) {
        log.Printf("manual resolution needed for %s", c.Field)
        continue
    }
    if err := s.ResolveConflicts(ctx, c.Field, strategy); err != nil { ... }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// verify schemas are compatible before cross-peer sync
if err := schemaCompatible(localSchema(), peerSchema(peer)); err != nil {
    return fmt.Errorf("skip sync: %w", err)
}

Try / catch

result, err := store.Sync(ctx, peer, "ours")
if err != nil && strings.Contains(err.Error(), "conflict resolution failed for ") {
    field := extractField(err.Error())
    log.Printf("manual resolution needed for field %q from peer %s", field, peer)
    abortMergeIfNeeded(ctx, store)
}

Prevention

When it happens

Trigger: store.Sync(ctx, peer, strategy) where ResolveConflicts errors for a specific c.Field: the field key is unknown to the conflict resolver, the underlying SQL update against dolt_conflicts fails, or the merge state changed concurrently.

Common situations: Schema drift between peers introduced a field the resolver doesn't know; a concurrent process aborted the merge mid-resolution; storage error while writing the resolved value.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/0de94fa5d4c0f3cd. Report an issue: GitHub.