mattermost-community/focalboard · error

cannot replace blockID %s: %w

Error message

cannot replace blockID %s: %w

What it means

Error from SQLStore.RunUniqueIDsMigration (server/services/store/sqlstore/data_migrations.go:115). For each block with a duplicate ID, the migration generates a new ID and calls replaceBlockID(tx, ...) to rewrite the ID across all affected tables within the transaction. Any failure is wrapped with the offending old block ID, after rolling back the whole migration transaction.

Source

Thrown at server/services/store/sqlstore/data_migrations.go:115

	blocksByID := map[string][]*model.Block{}
	for _, block := range blocks {
		blocksByID[block.ID] = append(blocksByID[block.ID], block)
	}

	for _, blocks := range blocksByID {
		for i, block := range blocks {
			if i == 0 {
				// do nothing for the first ID, only updating the others
				continue
			}

			newID := utils.NewID(model.BlockType2IDType(block.Type))
			if err := s.replaceBlockID(tx, block.ID, newID, block.WorkspaceID); err != nil {
				if rollbackErr := tx.Rollback(); rollbackErr != nil {
					s.logger.Error("Unique IDs transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "replaceBlockID"))
				}
				return fmt.Errorf("cannot replace blockID %s: %w", block.ID, err)
			}
		}
	}

	if err := s.setSystemSetting(tx, UniqueIDsMigrationKey, strconv.FormatBool(true)); err != nil {
		if rollbackErr := tx.Rollback(); rollbackErr != nil {
			s.logger.Error("Unique IDs transaction rollback error", mlog.Err(rollbackErr), mlog.String("methodName", "setSystemSetting"))
		}
		return fmt.Errorf("cannot mark migration as completed: %w", err)
	}

	if err := tx.Commit(); err != nil {
		return fmt.Errorf("cannot commit unique IDs transaction: %w", err)
	}

	s.logger.Debug("Unique IDs migration finished successfully")
	return nil
}

View on GitHub (pinned to a84bbb65e3)

Solutions

  1. Inspect the wrapped cause for the failing block ID to find which table/constraint rejected the ID replacement.
  2. Search the schema for tables still referencing the old block ID and update or clean them manually, then re-run the migration.
  3. Restore referential integrity (orphaned child rows) before retrying; the transaction rolls back so state is consistent.
  4. Run during a maintenance window to avoid lock contention and timeouts.
  5. Back up the database before running this migration on large or hand-modified datasets.

Example fix

// before
SELECT * FROM information_schema.foreign_keys -- after failure on blockID X
-- after
-- find orphaned references and repair, then retry:
DELETE FROM history_blocks WHERE block_id = 'X' AND parent_id IS NULL;
-- re-run
store.RunUniqueIDsMigration()
Defensive patterns

Strategy: validation

Validate before calling

-- before running the migration, check for orphaned references:
SELECT hb.block_id FROM history_blocks hb
LEFT JOIN blocks b ON b.id = hb.block_id
WHERE b.id IS NULL;

Type guard

func isReplaceBlockIDError(err error) (string, bool) {
	if err != nil && strings.Contains(err.Error(), "cannot replace blockID ") {
		parts := strings.SplitN(err.Error(), "cannot replace blockID ", 2)
		id := strings.Fields(parts[1])[0]
		return id, true
	}
	return "", false
}

Try / catch

err := store.RunUniqueIDsMigration()
if err != nil {
	if blockID, ok := isReplaceBlockIDError(err); ok {
		// inspect all tables referencing blockID, repair rows, then re-run
		log.Printf("failed replacing block %s: %v", blockID, errors.Unwrap(err))
	}
	return err
}

Prevention

When it happens

Trigger: Running the unique-IDs migration when replaceBlockID fails for a specific duplicated block: FK constraint violations on child tables referencing the block ID, schema drift (a table still referencing the old ID without an update path), statement timeout, or connection loss mid-transaction.

Common situations: Instances with manually manipulated databases or restored partial backups where referential integrity is broken; plugins/older versions that added their own tables keyed by block ID which replaceBlockID does not know about; very large block histories causing per-update timeouts.

Related errors


AI-assisted analysis of mattermost-community/focalboard@a84bbb65e3 (2026-08-30). Data as JSON: /api/errors/77a0411a401258d8. Report an issue: GitHub.