What’s broken?
The AI feature does not seems to work since the version 0.52.1 (probably 0.52.0 as well).
The problem seems to occur when a document is collaborative (with withCollaboration ).
What did you expect to happen?
The AI should work when the doc is collaborative as well.
Steps to reproduce
- Open a collaborative document with a single paragraph, e.g.
Bonjour le monde. - Select the whole paragraph.
- Open the AI menu and run "Improve writing" (any prompt that returns a single
update operation on the selected block works). - The LLM responds with a perfectly valid tool call, e.g.:
{"type":"update","id":"<blockId>$","block":"<p>Bonjour à tous</p>"} - Applying this operation intermittently throws, and the AI menu shows a generic error even though the LLM call itself succeeded (
chat.status === "ready", chat.error === undefined).
BlockNote version
v0.52.1
Environment
No response
Additional context
AI Report
Observed errors
Reproduced twice from the same input/action, with two different stack traces:
A) "html diff"
ChunkExecutionError: Tool execution failed: html diff
at Object.transform (StreamToolExecutor.ts:131:15)
Caused by: Error: html diff
at Object.Pe [as rebaseTool] (rebaseTool.ts:51:15)
at Object.execute (createUpdateBlockTool.ts:205:33)
at Object.transform (StreamToolExecutor.ts:123:43)
This is the self-consistency check in createHTMLRebaseTool (src/api/formats/html-blocks/tools/rebaseTool.ts), which round-trips the current block through blocksToHTMLLossy → tryParseHTMLToBlocks and throws if updateToReplaceSteps finds any diff between the original and the round-tripped version.
B) "Position not found, cannot track positions"
ChunkExecutionError: Tool execution failed: Position not found, cannot track positions
at Object.transform (StreamToolExecutor.ts:131:15)
Caused by: Error: Position not found, cannot track positions
at Object.from (RelativePositionMapping.ts:63:23)
at Object.execute (createUpdateBlockTool.ts:208:64)
at Object.transform (StreamToolExecutor.ts:123:43)
This comes from RelativePositionMappingExtension.mapPosition() (@blocknote/core/src/yjs/extensions/RelativePositionMapping.ts), used by createUpdateBlockTool to track the updateSelection.from/to positions (via trackPosition()) across the async LLM round-trip. relativePositionToAbsolutePosition returns null — per the code's own comment, "This can happen if the element is garbage collected."
Analysis
createUpdateBlockTool's outer factory calls trackPosition(editor, options.updateSelection.from/to) once, at stream-tool creation time (before the LLM responds). In a Yjs-collaborative document, this resolves to a relative Yjs position anchored via RelativePositionMappingExtension. Later, inside execute() (createUpdateBlockTool.ts:207-212), the tracked position is resolved via tool.invertMap.invert().map(selectionPositions.from()).
Because the "update" operation being applied is itself replacing content within (or very near) the same selection range that was anchored, it looks like the act of applying the update can invalidate/GC the Yjs relative-position anchor it's simultaneously trying to resolve — a self-referential race. This would explain why the same input intermittently produces either the position error directly, or the "html diff" self-check tripping (likely because the document is observed mid-mutation by the rebase's clean-projection logic).
⚠️ This wouldn't show up in a non-collaborative setup, since updateSelection positions would fall back to plain ProseMirror position mapping (see the ySyncPluginState.binding.type.length === 0 fallback path in the same file), which doesn't have this GC-based failure mode.
Impact
The failure is silent/confusing from the app's perspective: the LLM call succeeds fully (chat.status: "ready", no chat.error), but the returned, well-formed tool call fails to apply, and AIExtension only logs console.warn("Error calling LLM", ...) — no user-facing detail. Since it's intermittent (same input, two different stack traces across two attempts), it also doesn't repro reliably outside a live collaborative session, which made isolating it harder — a synthetic (non-live-DOM) BlockNoteEditor.create() reproduction with the exact same schema/config and exact same operation did not reproduce the crash, suggesting it depends on live collaborative/DOM state.
Contribution
Sponsor
What’s broken?
The AI feature does not seems to work since the version 0.52.1 (probably 0.52.0 as well).
The problem seems to occur when a document is collaborative (with
withCollaboration).What did you expect to happen?
The AI should work when the doc is collaborative as well.
Steps to reproduce
Bonjour le monde.updateoperation on the selected block works).{"type":"update","id":"<blockId>$","block":"<p>Bonjour à tous</p>"}chat.status === "ready",chat.error === undefined).BlockNote version
v0.52.1
Environment
No response
Additional context
AI Report
Observed errors
Reproduced twice from the same input/action, with two different stack traces:
A) "html diff"
This is the self-consistency check in
createHTMLRebaseTool(src/api/formats/html-blocks/tools/rebaseTool.ts), which round-trips the current block throughblocksToHTMLLossy→tryParseHTMLToBlocksand throws ifupdateToReplaceStepsfinds any diff between the original and the round-tripped version.B) "Position not found, cannot track positions"
This comes from
RelativePositionMappingExtension.mapPosition()(@blocknote/core/src/yjs/extensions/RelativePositionMapping.ts), used bycreateUpdateBlockToolto track theupdateSelection.from/topositions (viatrackPosition()) across the async LLM round-trip.relativePositionToAbsolutePositionreturnsnull— per the code's own comment, "This can happen if the element is garbage collected."Analysis
createUpdateBlockTool's outer factory callstrackPosition(editor, options.updateSelection.from/to)once, at stream-tool creation time (before the LLM responds). In a Yjs-collaborative document, this resolves to a relative Yjs position anchored viaRelativePositionMappingExtension. Later, insideexecute()(createUpdateBlockTool.ts:207-212), the tracked position is resolved viatool.invertMap.invert().map(selectionPositions.from()).Because the "update" operation being applied is itself replacing content within (or very near) the same selection range that was anchored, it looks like the act of applying the update can invalidate/GC the Yjs relative-position anchor it's simultaneously trying to resolve — a self-referential race. This would explain why the same input intermittently produces either the position error directly, or the "html diff" self-check tripping (likely because the document is observed mid-mutation by the rebase's clean-projection logic).
updateSelectionpositions would fall back to plain ProseMirror position mapping (see theySyncPluginState.binding.type.length === 0fallback path in the same file), which doesn't have this GC-based failure mode.Impact
The failure is silent/confusing from the app's perspective: the LLM call succeeds fully (
chat.status: "ready", nochat.error), but the returned, well-formed tool call fails to apply, andAIExtensiononly logsconsole.warn("Error calling LLM", ...)— no user-facing detail. Since it's intermittent (same input, two different stack traces across two attempts), it also doesn't repro reliably outside a live collaborative session, which made isolating it harder — a synthetic (non-live-DOM)BlockNoteEditor.create()reproduction with the exact same schema/config and exact same operation did not reproduce the crash, suggesting it depends on live collaborative/DOM state.Contribution
Sponsor