gastownhall/beads · error
table %s is not union-mergeable
Error message
table %s is not union-mergeable
What it means
unionConflictsAreSafe only auto-resolves conflicts for tables in the unionConflictKeyColumns allowlist (labels, comments, events). If a table outside that allowlist is passed in, there is no unambiguous 'union' resolution for it, so the function refuses with this error instead of guessing. It is an internal safety guard, not a data problem.
Source
Thrown at internal/storage/versioncontrolops/automerge.go:641
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
}
if declineDuplicateConflictRows(table, keyCols, rows) {
return nil, false, nil
}
plan := make([]unionRowKey, 0, len(rows))
for _, row := range rows {
key, ok := unionRowIsSafe(row, keyCols)
if !ok {
return nil, false, nil
}
plan = append(plan, key)
}
return plan, true, nil
}View on GitHub (pinned to 71377f2769)
Solutions
- Check that the table name passed to TryAutoResolveMergeConflicts exactly matches a key in unionConflictKeyColumns (labels, comments, events).
- If a new union-merged table was added, register its primary-key columns in unionConflictKeyColumns (automerge.go:68) and run TestUnionConflictKeyColumnsCoverTheUnionTables.
- For tables like issues that need per-cell resolution, route them to their dedicated resolver (resolveIssuesFieldMerge) instead of the union path.
- Otherwise treat it as expected: leave the conflict for manual operator resolution with dolt_conflicts_resolve.
Example fix
// before
TryAutoResolveMergeConflicts(ctx, db, []string{"labels", "issue_labels"})
// after — only pass allowlisted union tables
TryAutoResolveMergeConflicts(ctx, db, []string{"labels", "comments", "events"}) Defensive patterns
Strategy: validation
Validate before calling
// only pass union-merged tables
var unionTables = map[string]bool{"labels": true, "comments": true, "events": true}
func canAutoResolve(table string) bool { return unionTables[table] }
if !canAutoResolve(table) { /* skip auto-resolve, leave for manual dolt_conflicts_resolve */ } Type guard
func isUnionMergeable(table string) bool {
switch table {
case "labels", "comments", "events":
return true
}
return false
} Prevention
- Only call TryAutoResolveMergeConflicts with tables listed in unionConflictKeyColumns.
- When adding a new union-merged table, add its key columns to the allowlist and keep TestUnionConflictKeyColumnsCoverTheUnionTables passing.
- Route issues-table conflicts to the dedicated field-merge resolver, not the union path.
- Treat this error as a signal to fall back to manual dolt_conflicts_resolve.
When it happens
Trigger: TryAutoResolveMergeConflicts iterates conflicted tables during an automatic merge resolution and reaches a table not present in the unionConflictKeyColumns map (anything other than labels, comments, or events), or a future/renamed table was added to the merge path without a matching allowlist entry.
Common situations: A new table was added to the merge flow but the developer forgot to register its key columns in unionConflictKeyColumns; a table name typo or rename broke the map lookup; custom code calls TryAutoResolveMergeConflicts with an unsupported table such as 'issues', which has its own dedicated field-merge resolver.
Related errors
- unexpected %s conflict row with no our_%s (safety check bypa
- clear %s conflict: %w
- a %s conflict was not cleared (no conflict row deleted)
- dolt directory is required
- dolt binary not found
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/58cf328d253fe9aa.
Report an issue: GitHub.