gastownhall/beads · error

re-key id %s -> %s: %w

Error message

re-key id %s -> %s: %w

What it means

This error wraps a failed per-row UPDATE that rewrites an auxiliary table's `id` from an old random ID to a new deterministic content-digest-derived ID during the aux row-ID backfill. It carries both the old and new IDs plus the underlying driver error (constraint violation, connection loss, etc.). Each UPDATE is a standalone statement, so a failure mid-pass leaves the table partially re-keyed; the sentinel mechanism allows resume on the next run.

Source

Thrown at internal/storage/schema/aux_row_id_backfill.go:349

		}
		sort.Strings(free)
		i := 0
		for _, target := range targets {
			if held[target] {
				continue
			}
			todo = append(todo, rekey{oldID: free[i], newID: target})
			i++
		}
	}
	// Deterministic UPDATE order (groups is a map) so runs are reproducible.
	sort.Slice(todo, func(i, j int) bool { return todo[i].oldID < todo[j].oldID })

	for _, r := range todo {
		//nolint:gosec // G201: table name is a hardcoded constant, never user input.
		if _, err := db.ExecContext(ctx, fmt.Sprintf(`UPDATE %s SET id = ? WHERE id = ?`, t.name),
			r.newID, r.oldID); err != nil {
			return true, fmt.Errorf("re-key id %s -> %s: %w", r.oldID, r.newID, err)
		}
	}
	return len(todo) > 0, nil
}

View on GitHub (pinned to 71377f2769)

Solutions

  1. Re-run MigrateUp: the pass is resumable via the in-progress sentinel and idempotent per digest group.
  2. If duplicate-key errors repeat, inspect the aux table for rows already holding the target id and remove/merge stale duplicates before rerunning.
  3. Confirm the table's `id` column is CHAR(36) (post-migration 0037); ensure base migrations complete first.
  4. Avoid concurrent writers against the database while migrations run (use the migration lock path).

Example fix

// before: one bad row aborts the whole pass
if _, err := db.ExecContext(ctx, fmt.Sprintf(`UPDATE %s SET id = ? WHERE id = ?`, t.name), r.newID, r.oldID); err != nil {
	return true, fmt.Errorf("re-key id %s -> %s: %w", r.oldID, r.newID, err)
}
// after: clear any row already occupying the deterministic target id first
db.ExecContext(ctx, fmt.Sprintf(`DELETE FROM %s WHERE id = ? AND id <> ?`, t.name), r.newID, r.oldID)
if _, err := db.ExecContext(ctx, fmt.Sprintf(`UPDATE %s SET id = ? WHERE id = ?`, t.name), r.newID, r.oldID); err != nil {
	return true, fmt.Errorf("re-key id %s -> %s: %w", r.oldID, r.newID, err)
}
Defensive patterns

Strategy: retry

Validate before calling

// Verify the table and id column are in the expected state before migrating
var colType string
err := db.QueryRowContext(ctx,
	`SELECT DATA_TYPE FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND COLUMN_NAME = 'id'`,
	tableName).Scan(&colType)
if err == nil && colType != "char" {
	return fmt.Errorf("table %s.id is %s, expected CHAR(36); run base migrations first", tableName, colType)
}

Try / catch

err := migrateUp(ctx)
if err != nil && strings.Contains(err.Error(), "re-key id ") {
	// partial pass is resumable; serialize writes and retry
	drainWriters()
	err = migrateUp(ctx)
}

Prevention

When it happens

Trigger: rekeyAuxRowTable executes `UPDATE <table> SET id = ? WHERE id = ?` for each pending row and the statement errors — duplicate-key conflict when the new deterministic ID already exists, connection drop, context cancellation, or the id column not being the expected CHAR(36) type.

Common situations: Digest-derived target id colliding with an existing row; migration racing concurrent writes to the aux table; database upgraded from a pre-0037 schema where `id` isn't CHAR(36); killed Dolt server mid-pass.

Related errors


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