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

  1. Always build the plan via unionConflictsAreSafe (which calls unionRowIsSafe) — never construct unionRowKey values by hand.
  2. Re-run the merge/auto-resolve so the check pass and resolution pass operate on the same schema snapshot.
  3. Inspect dolt_conflicts_<table> for NULL our_<keycol> values; a NULL key indicates a deeper schema-merge problem to resolve manually.
  4. 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

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


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