gastownhall/beads · error

query conflict for %s %s: %w

Error message

query conflict for %s %s: %w

What it means

loadConflictRow failed while querying dolt_conflicts_<table> for a single row by key, matching either our_<keyCol> or their_<keyCol>. This wraps the underlying query error, meaning the SELECT itself failed to execute — connection, permission, or server-side failure — not merely absence of the row.

Source

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

	for i, c := range r.cols {
		side, field, ok := splitConflictColumn(c)
		if !ok || side != "their" || conflictMetaSuffixes[field] || field == keyCol {
			continue
		}
		names = append(names, field)
		vals = append(vals, r.vals[i])
	}
	return names, vals
}

// loadConflictRow fetches the single conflict row for key. It matches on
// either side's key column so a row only one side has is *found* and then
// refused with a precise message, rather than reported as "no conflict".
func loadConflictRow(ctx context.Context, db DBConn, table, keyCol, key string) (rawConflictRow, error) {
	q := fmt.Sprintf("SELECT * FROM `dolt_conflicts_%s` WHERE `our_%s` = ? OR `their_%s` = ?", table, keyCol, keyCol) //nolint:gosec // identifiers validated
	rows, err := db.QueryContext(ctx, q, key, key)
	if err != nil {
		return rawConflictRow{}, fmt.Errorf("query conflict for %s %s: %w", table, key, err)
	}
	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]
	}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Check the wrapped error for root cause (timeout vs connection vs SQL error)
  2. Retry the key's resolution; per-row resolution is idempotent once already resolved
  3. Extend the context deadline when resolving many keys
  4. Ensure no other session is resolving the same conflicts concurrently

Example fix

// before
ctx := context.Background()
for _, key := range keys { ResolveConflictRows(ctx, db, table, []string{key}, strat) }
// after
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(len(keys))*500*time.Millisecond)
defer cancel()
Defensive patterns

Strategy: retry

Try / catch

n, err := versioncontrolops.ResolveConflictRows(ctx, db, table, keys, strat)
if err != nil && strings.Contains(err.Error(), "query conflict for "+table) {
    if isTransient(err) {
        n, err = versioncontrolops.ResolveConflictRows(ctx, db, table, keys, strat) // idempotent retry
    }
}

Prevention

When it happens

Trigger: Called from ResolveConflictRows per key; the SELECT fails because the connection dropped, the context was cancelled, the session lost the conflict-tolerance flags, or the server rejected the query.

Common situations: Long per-key resolution loops exceeding context deadline; server restart mid-loop; the conflicted table was resolved concurrently by another session, invalidating conflict metadata.

Understand the failure class

Background: "query failed", "%w: SQL error" — wrapped database query errors in Go libraries explained — this error's family across 3 libraries.

Related errors


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