gastownhall/beads · error

scan conflict for %s %s: %w

Error message

scan conflict for %s %s: %w

What it means

The driver failed to Scan the conflict row's columns into []any destinations during loadConflictRow. This wraps the raw driver scan error (e.g. unsupported column type or driver-internal conversion failure). It means the conflict row exists but its contents could not be materialized for row-level resolution.

Source

Thrown at internal/storage/versioncontrolops/conflicts.go:329

	defer func() { _ = rows.Close() }()

	cols, err := rows.Columns()
	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

View on GitHub (pinned to 71377f2769)

Solutions

  1. Read the wrapped driver error to identify the offending column type
  2. Upgrade/align the go-sql-driver/mysql version with the dolt server version
  3. Resolve the conflict with a whole-table strategy (--theirs/--ours on the table) instead of row level
  4. Reproduce the SELECT manually in a dolt sql shell to inspect the conflicting value

Example fix

// before
resolveOne(ctx, db, "issues", "id", key, "theirs") // scan fails on JSON col
// after
resolveTable(ctx, db, "issues", "theirs") // whole-table strategy avoids row scan
Defensive patterns

Strategy: fallback

Validate before calling

// probe the conflict table's column types before row-level resolution
rows, _ := db.QueryContext(ctx, "SELECT * FROM dolt_conflicts_"+table+" LIMIT 1")
cols, _ := rows.Columns(); _ = rows.Close()
// if exotic types are present, prefer whole-table strategy

Try / catch

err := resolveOne(ctx, db, table, keyCol, key, "theirs")
if err != nil && strings.Contains(err.Error(), "scan conflict for") {
    // fall back to whole-table resolution
    return resolveWholeTable(ctx, db, table, "theirs")
}
return err

Prevention

When it happens

Trigger: ResolveConflictRows -> loadConflictRow scans `SELECT *` output of dolt_conflicts_<table>; a column type the MySQL driver cannot scan into interface{} (e.g. certain BLOB/JSON/decimal encodings or a corrupted value) causes rows.Scan to fail.

Common situations: Tables with exotic column types (JSON, large BLOBs, bit/newer-decimal types) after a dolt version change; driver version mismatch with the dolt sql-server wire format; corrupted conflict metadata after a crash.

Related errors


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