toeverything/AFFiNE · error · BlockSuiteError

ModelCRUDError

ModelCRUDError

Error message

updating block: ${model.id} not found

What it means

After resolving the model, updateBlock fetches the underlying Yjs map with _yBlocks.get(model.id). A model object that survives in memory while its Y data is gone (deleted block, detached model, concurrent remote delete) produces this ModelCRUDError variant — the model exists but the backing store entry does not.

Solutions

  1. Treat deleteBlock as invalidating all model references — re-fetch with doc.getBlock(model.id) before every update
  2. Guard updates with the Y map existence check or wrap updateBlock in try/catch for BlockSuiteError
  3. Dispose UI state bound to a model when its deleted subject fires

Example fix

// before
const model = getSomeCachedModel(); // block deleted meanwhile
doc.updateBlock(model, { type: 'h1' }); // -> updating block: <id> not found

// after
const block = doc.getBlock(model.id);
if (!block) return;
doc.updateBlock(block.model, { type: 'h1' });
Defensive patterns

Strategy: validation

Validate before calling

// model may be stale even if the object still exists in memory
const block = doc.getBlock(model.id);
if (block) {
  doc.updateBlock(block.model, props);
}

Type guard

function isLiveModel(doc: Store, model: BlockModel): boolean {
  return doc.hasBlock(model.id);
}

Try / catch

try {
  doc.updateBlock(model, props);
} catch (e) {
  if (e instanceof BlockSuiteError && e.code === ErrorCode.ModelCRUDError) {
    // remote deletion won the race: discard the edit, refresh from doc.getBlock
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Holding a BlockModel after doc.deleteBlock(model) and calling doc.updateBlock(model, ...); a remote peer deleting the block between your read and your write; models captured by UI components that outlive their block.

Common situations: Event handlers firing after block deletion (e.g. blur/format toolbar on a removed block); undo/redo races; component trees not re-rendering after deletion.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/d38051126fee60ec. Report an issue: GitHub.

Appendix: source

Thrown at blocksuite/framework/store/src/model/store/store.ts:893

    if (!model) {
      throw new BlockSuiteError(
        ErrorCode.ModelCRUDError,
        `updating block: ${modelOrId} not found`
      );
    }

    if (!isCallback) {
      const parent = this.getParent(model);
      this.schema.validate(
        model.flavour,
        parent?.flavour,
        callBackOrProps.children?.map(child => child.flavour)
      );
    }

    const yBlock = this._yBlocks.get(model.id);
    if (!yBlock) {
      throw new BlockSuiteError(
        ErrorCode.ModelCRUDError,
        `updating block: ${model.id} not found`
      );
    }

    const block = this.getBlock(model.id);
    if (!block) return;

    this.transact(() => {
      if (isCallback) {
        callBackOrProps();
        this._runQuery(block);
        return;
      }

      if (callBackOrProps.children) {
        this._crud.updateBlockChildren(
          model.id,

View on GitHub (pinned to b4c8548c09)