{"record":{"id":"235231c36a2196d5","repo":"gastownhall/beads","slug":"multiple-conflict-rows-for-s-s-resolve-the-whol","errorCode":null,"errorMessage":"multiple conflict rows for %s %s; resolve the whole table instead","messagePattern":"multiple conflict rows for (.+?) (.+?); resolve the whole table instead","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/storage/versioncontrolops/conflicts.go","lineNumber":332,"sourceCode":"\tif err != nil {\n\t\treturn rawConflictRow{}, fmt.Errorf(\"conflict columns for table %s: %w\", table, err)\n\t}\n\tif !rows.Next() {\n\t\tif err := rows.Err(); err != nil {\n\t\t\treturn rawConflictRow{}, fmt.Errorf(\"query conflict for %s %s: %w\", table, key, err)\n\t\t}\n\t\treturn rawConflictRow{}, fmt.Errorf(\"no live conflict for %s %s\", table, key)\n\t}\n\tvals := make([]any, len(cols))\n\tptrs := make([]any, len(cols))\n\tfor i := range vals {\n\t\tptrs[i] = &vals[i]\n\t}\n\tif err := rows.Scan(ptrs...); err != nil {\n\t\treturn rawConflictRow{}, fmt.Errorf(\"scan conflict for %s %s: %w\", table, key, err)\n\t}\n\tif rows.Next() {\n\t\treturn rawConflictRow{}, fmt.Errorf(\"multiple conflict rows for %s %s; resolve the whole table instead\", table, key)\n\t}\n\treturn rawConflictRow{cols: cols, vals: vals}, errors.Join(rows.Err(), rows.Close())\n}\n\n// conflictTargetStillPresent reports whether key still names a row of table.\n//\n// It is the matched-rows check the resolvers need after a write, because\n// RowsAffected is rows CHANGED, not rows MATCHED: the DSN sets parseTime and\n// multiStatements but NOT clientFoundRows (doltutil/dsn.go), so an UPDATE the\n// backend normalizes to the bytes already stored reports zero exactly as a\n// vanished row does. Only asking can tell the two apart.\n//\n// It confirms that the key still resolves to a row — NOT that our values are\n// the stored ones. On the autocommit path (an embedded Pull, where db is not a\n// transaction) a row deleted and re-inserted between the UPDATE and this check\n// would read as present; in server mode the caller holds a transaction and the\n// window does not exist. Comparing the written values instead would reintroduce\n// the very normalization sensitivity this check exists to absorb.","sourceCodeStart":314,"sourceCodeEnd":350,"githubUrl":"https://github.com/gastownhall/beads/blob/71377f276968b452ee607177637970a4ff888584/internal/storage/versioncontrolops/conflicts.go#L314-L350","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nresolveOne(ctx, db, \"issues\", \"slug\", \"dup-slug\", \"theirs\") // multiple rows\n// after\nresolveOne(ctx, db, \"issues\", \"id\", \"42\", \"theirs\") // resolve by primary key","handlingStrategy":"validation","validationCode":"// ensure the resolve key is the primary key before calling row-level API\nvar pk string\ndb.QueryRowContext(ctx,\n  \"SELECT COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_NAME=? AND CONSTRAINT_NAME='PRIMARY'\",\n  table).Scan(&pk)\nif pk != keyCol { return fmt.Errorf(\"resolve by primary key %s, not %s\", pk, keyCol) }","typeGuard":null,"tryCatchPattern":"err := resolveOne(ctx, db, table, keyCol, key, strategy)\nif err != nil && strings.Contains(err.Error(), \"multiple conflict rows for\") {\n    return resolveWholeTable(ctx, db, table, strategy)\n}\nreturn err","preventionTips":["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"],"tags":["database","dolt","conflict-resolution","unambiguous-key"],"backgroundTag":"multiple-conflict-rows","analyzedSha":"71377f276968b452ee607177637970a4ff888584","analyzedAt":"2026-08-30T18:55:39.744Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}