gastownhall/beads · error
unexpected %s conflict row with no our_%s (safety check bypa
Error message
unexpected %s conflict row with no our_%s (safety check bypassed)
What it means
While building the DELETE for a validated conflict row, a key-column value read from the our_ side is nil. unionRowIsSafe should have guaranteed both sides exist and agree, so a nil here means the safety check was bypassed or the plan was built from stale/malformed data. The function aborts rather than delete with a NULL predicate that could clear the wrong (or no) conflict rows.
Source
Thrown at internal/storage/versioncontrolops/automerge.go:707
type unionRowKey struct {
columns []string
values []any
}
// resolveUnionConflicts settles the conflicts unionConflictsAreSafe validated.
// Both sides hold the same row, so our working set already carries the union:
// deleting the conflict row is the whole resolution.
func resolveUnionConflicts(ctx context.Context, db DBConn, table string, plan []unionRowKey) error {
if _, ok := unionConflictKeyColumns[table]; !ok {
return fmt.Errorf("table %s is not union-mergeable", table)
}
for _, row := range plan {
preds := make([]string, 0, len(row.columns))
args := make([]any, 0, len(row.columns))
for i, k := range row.columns {
v := row.values[i]
if v == nil {
return fmt.Errorf("unexpected %s conflict row with no our_%s (safety check bypassed)", table, k)
}
preds = append(preds, "`our_"+k+"` = ?")
args = append(args, v)
}
//nolint:gosec // table and key columns come from the unionConflictKeyColumns allowlist.
stmt := "DELETE FROM `dolt_conflicts_" + table + "` WHERE " + strings.Join(preds, " AND ")
res, err := db.ExecContext(ctx, stmt, args...)
if err != nil {
return fmt.Errorf("clear %s conflict: %w", table, err)
}
if n, err := res.RowsAffected(); err == nil && n == 0 {
return fmt.Errorf("a %s conflict was not cleared (no conflict row deleted)", table)
}
}
return nil
}
View on GitHub (pinned to 71377f2769)
Solutions
- Always build the plan via unionConflictsAreSafe (which calls unionRowIsSafe) — never construct unionRowKey values by hand.
- Re-run the merge/auto-resolve so the check pass and resolution pass operate on the same schema snapshot.
- Inspect dolt_conflicts_<table> for NULL our_<keycol> values; a NULL key indicates a deeper schema-merge problem to resolve manually.
- If schema drift is the cause, settle the schema conflict first, then retry conflict resolution.
Example fix
// before — plan built without validation
plan := []unionRowKey{{columns: []string{"issue_id", "label"}, values: []any{nil, "bug"}}}
resolveUnionConflicts(ctx, db, "labels", plan)
// after
plan, safe, err := unionConflictsAreSafe(ctx, db, "labels")
if err != nil { return err }
if !safe { return errors.New("conflicts not auto-resolvable") }
resolveUnionConflicts(ctx, db, "labels", plan) Defensive patterns
Strategy: validation
Validate before calling
// validate plan keys before resolving
for _, row := range plan {
for i, v := range row.values {
if v == nil {
return fmt.Errorf("plan row %d has nil key value for %s", i, row.columns[i])
}
}
} Type guard
func planKeysPresent(plan []unionRowKey) bool {
for _, row := range plan {
for _, v := range row.values {
if v == nil { return false }
}
}
return true
} Prevention
- Build plans exclusively through unionConflictsAreSafe/unionRowIsSafe.
- Re-run the check pass and resolution atomically against the same schema snapshot.
- Resolve schema (column-set) conflicts before row conflict resolution.
- Never hand-construct unionRowKey values in production code.
When it happens
Trigger: A unionRowKey in the plan has a nil entry in values for one of the key columns — e.g. the plan was constructed outside unionConflictsAreSafe, the underlying dolt_conflicts_<table> schema changed between the check pass and resolution, or a schema merge altered the our_<key> columns so the value read back as NULL.
Common situations: Concurrent schema merge extended or renamed key columns between loadConflictRows and the DELETE; hand-built plans in tests or new code paths skip unionRowIsSafe; a peer repo's schema merge changed the conflict table's column set.
Related errors
- table %s is not union-mergeable
- 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/16fa5339b023de8b.
Report an issue: GitHub.