gastownhall/beads · error

their values for %s %s matched no row (was it deleted concur

Error message

their values for %s %s matched no row (was it deleted concurrently?); conflict left unresolved

What it means

This error comes from resolveOneConflictRow when resolving a modify/modify dolt conflict with the 'theirs' strategy: the UPDATE writing the peer's values matched zero rows, and a follow-up existence check (conflictTargetStillPresent) confirmed the target row is genuinely gone from the working table. Since their values cannot be applied to a nonexistent row, the conflict row is deliberately left in dolt_conflicts_<table> and the resolution aborts rather than silently discarding one side.

Source

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

		stmt := fmt.Sprintf("UPDATE `%s` SET %s WHERE `%s` = ?", table, strings.Join(sets, ", "), keyCol) //nolint:gosec // identifiers validated above
		res, err := db.ExecContext(ctx, stmt, args...)
		if err != nil {
			return fmt.Errorf("apply their values for %s %s: %w", table, key, err)
		}
		// Zero rows would mean the row we read the conflict for is no longer
		// there — another session on the same branch deleted it between the
		// read and the write. Clearing the conflict now would discard their
		// side under a --theirs invocation, undetectably. But zero is not
		// proof of that on its own (see conflictTargetStillPresent), so ask
		// before refusing: an operator who named this row deserves the abort
		// only when the row really is gone.
		if n, err := res.RowsAffected(); err != nil || n == 0 {
			present, err := conflictTargetStillPresent(ctx, db, table, keyCol, ourKey)
			if err != nil {
				return fmt.Errorf("confirm %s %s still exists after writing their values: %w", table, key, err)
			}
			if !present {
				return fmt.Errorf("their values for %s %s matched no row (was it deleted concurrently?); conflict left unresolved", table, key)
			}
		}
	}

	del := fmt.Sprintf("DELETE FROM `dolt_conflicts_%s` WHERE `our_%s` = ?", table, keyCol) //nolint:gosec // identifiers validated
	res, err := db.ExecContext(ctx, del, ourKey)
	if err != nil {
		return fmt.Errorf("clear conflict for %s %s: %w", table, key, err)
	}
	if n, err := res.RowsAffected(); err == nil && n == 0 {
		return fmt.Errorf("conflict for %s %s was not cleared (no conflict row deleted)", table, key)
	}
	return nil
}

// GetMergeBlockers reports the merge state that `bd conflicts` cannot show as
// rows: whether a merge is open at all, plus the schema conflicts and
// constraint violations that make dolt refuse the merge commit even when

View on GitHub (pinned to 71377f2769)

Solutions

  1. Re-list the conflicts and retry only rows still present in both dolt_conflicts_<table> and the base table.
  2. Re-run the merge/pull so the conflict set reflects the current working set before applying a whole-table strategy.
  3. Use the 'ours' strategy for rows that legitimately no longer exist, or delete the conflict row explicitly if you intend to accept the deletion.
  4. Serialize resolution: ensure only one process resolves conflicts on a branch at a time (lock or a single bd session).

Example fix

// before: resolve stale rows by keys captured earlier
err := ops.ResolveConflictRows(ctx, db, table, "theirs", keys)
// after: re-verify each row exists before resolving
for _, k := range keys {
  var n int
  _ = db.QueryRowContext(ctx, fmt.Sprintf("SELECT COUNT(*) FROM `%s` WHERE `%s` = ?", table, keyCol), k).Scan(&n)
  if n == 0 { continue } // row gone; skip instead of aborting mid-batch
  err = ops.ResolveConflictRows(ctx, db, table, "theirs", []string{k})
}
Defensive patterns

Strategy: validation

Validate before calling

func rowExists(ctx context.Context, db *sql.DB, table, keyCol string, key any) (bool, error) {
  var n int
  err := db.QueryRowContext(ctx, fmt.Sprintf("SELECT COUNT(*) FROM `%s` WHERE `%s` = ?", table, keyCol), key).Scan(&n)
  return n > 0, err
}
// call before ResolveConflictRows and skip rows that no longer exist

Try / catch

if err := ops.ResolveConflictRows(ctx, db, table, "theirs", keys); err != nil {
  if strings.Contains(err.Error(), "matched no row") {
    return refreshAndRetry(ctx, db, table) // row vanished concurrently
  }
  return err
}

Prevention

When it happens

Trigger: Calling ResolveConflictRows with the 'theirs' strategy on a row whose key no longer exists in the working table — typically because another session or bd/dolt process deleted that row between the conflict-table read and the UPDATE. Also possible on the autocommit (embedded Pull) path where a delete+reinsert race window exists between reading the conflict and writing their values.

Common situations: Two operators resolving the same branch's merge conflicts at once; a cleanup job deleting rows concurrently with resolution; another process committing to the same working set; split-brain access to the same dolt database from server mode and embedded mode.

Related errors


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