toeverything/AFFiNE · critical

Failed to read image size

Error message

Failed to read image size

What it means

Every nested resolve increments a depth counter on the Resolver; reaching depth 100 throws RecursionLimitError. This guards factories that trigger unbounded re-entrant resolution — typically a component whose factory resolves the same identifier again in noCache mode (e.g. createEntity), where each level creates a fresh instance and the circular-dependency check (which keys on identifier already in the stack) is legitimately bypassed because each level is a new, uncached construction.

Solutions

  1. Stop self/looped instantiation: build child entities lazily (on method calls, after construction) instead of inside the factory/constructor
  2. Use provider.get (cached) instead of createEntity/noCache for recursive references so the cache pool breaks the chain
  3. If the chain is legitimately deep, flatten the construction so no single synchronous resolve nests 100+ levels

Example fix

// before
class Node extends Entity<{ id: string }> {
  children: Node[] = [];
  constructor(ids: string[], provider: FrameworkProvider) {
    super();
    this.children = ids.map(id => provider.createEntity(Node, { id })); // recursion -> limit
  }
}

// after
class Node extends Entity<{ id: string }> {
  children: Node[] = [];
  attach(child: Node) {
    this.children.push(child); // built imperatively, outside the factory
  }
}
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

import { RecursionLimitError } from '.../framework/core/error';

try {
  provider.createEntity(TreeNode, { id: 'root' });
} catch (err) {
  if (err instanceof RecursionLimitError) {
    // construction is recursive by design; rebuild lazily instead of retrying
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: A factory or constructor that calls provider.createEntity of its own identifier (self-reproducing entity); two factories that mutually createEntity each other; a factory that calls provider.get with noCache:true recursively; a genuinely linear dependency chain deeper than 100 components.

Common situations: Recursive structures (trees, graphs) modeled as Entities where each node constructs child nodes in its constructor; accidentally using createEntity instead of get inside a factory; generated/derived dependency graphs exceeding the depth cap.

Related errors


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

Appendix: source

Thrown at blocksuite/affine/blocks/image/src/utils.ts:239

  if (exceeded) {
    const size = formatSize(maxFileSize);
    toast(std.host, `You can only upload files less than ${size}`);
  }

  return exceeded;
}

async function buildPropsWith(std: BlockStdScope, file: File) {
  const { size } = file;
  const [imageSize, sourceId] = await Promise.all([
    readImageSize(file),
    std.store.blobSync.set(file),
  ]);

  if (!(imageSize.width * imageSize.height)) {
    toast(std.host, 'Failed to read image size, please try another image');
    throw new Error('Failed to read image size');
  }

  return { size, sourceId, ...imageSize } satisfies Partial<ImageBlockProps>;
}

export async function addSiblingImageBlocks(
  std: BlockStdScope,
  files: File[],
  targetModel: BlockModel,
  placement: 'after' | 'before' = 'after'
) {
  files = files.filter(file => file.type.startsWith('image/'));
  if (!files.length) return [];

  if (hasExceeded(std, files)) return [];

  const flavour = ImageBlockSchema.model.flavour;

View on GitHub (pinned to b4c8548c09)