gastownhall/beads · error

multiple conflict rows for %s %s; resolve the whole table in

Error message

multiple conflict rows for %s %s; resolve the whole table instead

What it means

loadConflictRow expected exactly one conflict row for the key but the SELECT in dolt_conflicts_<table> returned more than one. Dolt reports one conflict row per conflicting base row, so duplicates mean the key matches multiple conflict entries (e.g. key chosen too loosely or OR-matched across both sides' key columns). The library refuses rather than guessing which row to apply, and tells you to resolve at whole-table granularity.

Source

Thrown at internal/storage/versioncontrolops/conflicts.go:332

	if err != nil {
		return rawConflictRow{}, fmt.Errorf("conflict columns for table %s: %w", table, err)
	}
	if !rows.Next() {
		if err := rows.Err(); err != nil {
			return rawConflictRow{}, fmt.Errorf("query conflict for %s %s: %w", table, key, err)
		}
		return rawConflictRow{}, fmt.Errorf("no live conflict for %s %s", table, key)
	}
	vals := make([]any, len(cols))
	ptrs := make([]any, len(cols))
	for i := range vals {
		ptrs[i] = &vals[i]
	}
	if err := rows.Scan(ptrs...); err != nil {
		return rawConflictRow{}, fmt.Errorf("scan conflict for %s %s: %w", table, key, err)
	}
	if rows.Next() {
		return rawConflictRow{}, fmt.Errorf("multiple conflict rows for %s %s; resolve the whole table instead", table, key)
	}
	return rawConflictRow{cols: cols, vals: vals}, errors.Join(rows.Err(), rows.Close())
}

// conflictTargetStillPresent reports whether key still names a row of table.
//
// It is the matched-rows check the resolvers need after a write, because
// RowsAffected is rows CHANGED, not rows MATCHED: the DSN sets parseTime and
// multiStatements but NOT clientFoundRows (doltutil/dsn.go), so an UPDATE the
// backend normalizes to the bytes already stored reports zero exactly as a
// vanished row does. Only asking can tell the two apart.
//
// It confirms that the key still resolves to a row — NOT that our values are
// the stored ones. On the autocommit path (an embedded Pull, where db is not a
// transaction) a row deleted and re-inserted between the UPDATE and this check
// would read as present; in server mode the caller holds a transaction and the
// window does not exist. Comparing the written values instead would reintroduce
// the very normalization sensitivity this check exists to absorb.

View on GitHub (pinned to 71377f2769)

Solutions

  1. Run whole-table resolution (e.g. `bd conflicts --theirs <table>` at table level) instead of naming a single key
  2. Verify the key column is the table's primary key and unique across both branches
  3. For composite keys, supply the full key rather than a partial value
  4. Inspect `SELECT * FROM dolt_conflicts_<table>` to see the duplicate rows and resolve them individually

Example fix

// before
resolveOne(ctx, db, "issues", "slug", "dup-slug", "theirs") // multiple rows
// after
resolveOne(ctx, db, "issues", "id", "42", "theirs") // resolve by primary key
Defensive patterns

Strategy: validation

Validate before calling

// ensure the resolve key is the primary key before calling row-level API
var pk string
db.QueryRowContext(ctx,
  "SELECT COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_NAME=? AND CONSTRAINT_NAME='PRIMARY'",
  table).Scan(&pk)
if pk != keyCol { return fmt.Errorf("resolve by primary key %s, not %s", pk, keyCol) }

Try / catch

err := resolveOne(ctx, db, table, keyCol, key, strategy)
if err != nil && strings.Contains(err.Error(), "multiple conflict rows for") {
    return resolveWholeTable(ctx, db, table, strategy)
}
return err

Prevention

When it happens

Trigger: ResolveConflictRows -> loadConflictRow's `our_<keyCol> = ? OR their_<keyCol> = ?` predicate matches 2+ rows — typically when keyCol is not a true unique key (non-PK unique index, composite key resolved by only one column) or after add/add conflicts created two entries.

Common situations: Resolving by a non-primary-key column like a slug or email that is duplicated across branches; composite primary keys where only one part was supplied; conflicts from schema merges that changed the key structure.

Related errors


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