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
- Stop self/looped instantiation: build child entities lazily (on method calls, after construction) instead of inside the factory/constructor
- Use provider.get (cached) instead of createEntity/noCache for recursive references so the cache pool breaks the chain
- 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
- Never call createEntity (or get with noCache) for the same identifier inside a component's own factory/constructor
- Build recursive structures imperatively after construction, not during resolution
- Prefer cached provider.get for back-references so the cache pool terminates the chain
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)