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
- Run whole-table resolution (e.g. `bd conflicts --theirs <table>` at table level) instead of naming a single key
- Verify the key column is the table's primary key and unique across both branches
- For composite keys, supply the full key rather than a partial value
- 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
- Always resolve by the table's primary key
- Handle composite keys by resolving the whole table or all key parts
- Check for duplicate keys across branches before merging
- Treat non-unique columns as ineligible for row-level resolution
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
- query conflicts for table %s: %w
- query conflict for %s %s: %w
- no live conflict for %s %s
- conflict table dolt_conflicts_%s has no our_%s/their_%s colu
- conflict for %s %s is not a modify/modify conflict (one side
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/235231c36a2196d5.
Report an issue: GitHub.