gastownhall/beads · warning

conflict for issue %v was not cleared (no conflict row delet

Error message

conflict for issue %v was not cleared (no conflict row deleted)

What it means

After the UPDATE succeeded, the DELETE that clears dolt_conflicts_issues reported zero rows deleted (and no driver error). That means the conflict row this plan entry was built from no longer exists — typically because a concurrent process already resolved or removed it — so this code refuses to report success and aborts, since the plan is now stale.

Source

Thrown at internal/storage/versioncontrolops/automerge.go:627

			// normalizes to the bytes already stored also reports zero. Only a
			// follow-up existence check can tell "vanished" from "no-op".
			if n, err := res.RowsAffected(); err != nil || n == 0 {
				present, err := conflictTargetStillPresent(ctx, db, "issues", issuesKeyColumn, m.ourKey)
				if err != nil {
					return fmt.Errorf("confirm issue %v still exists after writing merged values: %w", m.ourKey, err)
				}
				if !present {
					return fmt.Errorf("merged values for issue %v matched no row (was it deleted concurrently?); conflict left unresolved", m.ourKey)
				}
			}
		}
		res, err := db.ExecContext(ctx,
			"DELETE FROM dolt_conflicts_issues WHERE our_"+issuesKeyColumn+" = ?", m.ourKey)
		if err != nil {
			return fmt.Errorf("clear conflict for issue %v: %w", m.ourKey, err)
		}
		if n, err := res.RowsAffected(); err == nil && n == 0 {
			return fmt.Errorf("conflict for issue %v was not cleared (no conflict row deleted)", m.ourKey)
		}
	}
	return nil
}

// unionConflictsAreSafe reports whether every live conflict of a union-merged
// table (labels, comments, events) is the same row on both sides with matching
// columns — the only class where "union" has an unambiguous answer. A row
// missing on one side (a deletion racing an insert) or diverging columns in a
// supposedly immutable row goes to the operator.
func unionConflictsAreSafe(ctx context.Context, db DBConn, table string) ([]unionRowKey, bool, error) {
	keyCols, ok := unionConflictKeyColumns[table]
	if !ok {
		return nil, false, fmt.Errorf("table %s is not union-mergeable", table)
	}
	rows, err := loadConflictRows(ctx, db, table)
	if err != nil {
		return nil, false, err

View on GitHub (pinned to 71377f2769)

Solutions

  1. Re-run the merge: reload the conflict list fresh so the plan matches current dolt_conflicts_issues state.
  2. Check for concurrent `bd`/agent sessions on the same database and serialize them (only one resolver at a time).
  3. Verify how the conflict actually got cleared (parent queries / dolt history) — if another resolver settled it correctly, the re-run should simply find no conflicts.
  4. If it recurs with no visible competitor, check for hooks or cron jobs running `bd` sync concurrently.

Example fix

// before: two processes can both auto-resolve the same merge
TryAutoResolveMergeConflicts(ctx, db)
// after: take an advisory lock / single-flight guard before resolving
if err := acquireMergeLock(ctx, db); err != nil {
    return fmt.Errorf("another resolver holds the merge lock: %w", err)
}
defer releaseMergeLock(ctx, db)
return TryAutoResolveMergeConflicts(ctx, db)
Defensive patterns

Strategy: retry

Validate before calling

// snapshot conflict keys, resolve, and confirm state did not move underneath
rows, err := loadConflictKeys(ctx, db, "issues")
if err != nil {
    return err
}
if len(rows) == 0 {
    return nil // nothing to resolve — a competitor already cleared them
}

Type guard

func isStaleConflictPlan(err error) bool {
    return err != nil && strings.Contains(err.Error(), "no conflict row deleted")
}

Try / catch

if err := TryAutoResolveMergeConflicts(ctx, db); err != nil {
    if isStaleConflictPlan(err) {
        // someone else resolved it; reload state and retry — second pass finds nothing
        return TryAutoResolveMergeConflicts(ctx, db)
    }
    return err
}

Prevention

When it happens

Trigger: Between loadConflictRows and the DELETE, another session cleared the conflict (ran its own auto-resolve or DOLT_CONFLICTS_RESOLVE), or Dolt collapsed the conflict row after the working-set UPDATE made the sides identical; the plan's key no longer matches any row in dolt_conflicts_issues.

Common situations: Two `bd` sessions auto-resolving the same pull simultaneously; a scheduled sync job racing an interactive merge; dolt version where conflict rows are auto-pruned once cells match; user manually resolving conflicts in a SQL shell while an agent runs auto-merge.

Related errors


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