siyuan-note/siyuan · error · Error

responseHTML.msg

Error message

responseHTML.msg

What it means

duplicateBlock fetches child heading DOM from the kernel via /api/block/getHeadingChildrenDOM. If the kernel returns a non-zero code, the raw server message string is thrown as a plain Error. It is a pass-through of a kernel API failure (e.g. block not found) into the frontend exception path.

Solutions

  1. Verify the block ID still exists (/api/block/getBlockInfo) before duplicating
  2. Re-query the DOM for a fresh data-node-id if the document was re-rendered
  3. Catch the error and show the kernel message, then reload the affected document
  4. Retry after the sync/index queue settles if a sync race is suspected

Example fix

// before
await duplicateBlock(item);
// after
try {
    await duplicateBlock(item);
} catch (e) {
    showMessage(e.message || "duplicate failed");
    reloadProtyle(protyle);
}
Defensive patterns

Strategy: try-catch

Validate before calling

const info = await fetchSyncPost("/api/block/getBlockInfo", {id: item.getAttribute("data-node-id")});
if (info.code !== 0) return; // block gone
await duplicateBlock(item);

Try / catch

try { await duplicateBlock(item); } catch (e) { showMessage(e.message); reloadProtyle(protyle); }

Prevention

When it happens

Trigger: Calling duplicateBlock (app/src/protyle/wysiwyg/commonHotkey.ts:365) on a heading item whose data-node-id no longer exists in the kernel — typically because the document was deleted/concurrently changed or the block ID is stale after an undo/redo or sync race.

Common situations: Duplicating a heading right after another client deleted its children via sync; stale DOM references after the block tree was re-indexed; kernel unavailable (workspace closed) while the UI still holds the node.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/c21810fabd2cc2db. Report an issue: GitHub.

Appendix: source

Thrown at app/src/protyle/wysiwyg/commonHotkey.ts:365

        lastElement.after(tempElement);
        doOperations.push({
            action: "insert",
            data: tempElement.outerHTML,
            id: newId,
            previousID: lastElement.getAttribute("data-node-id"),
        });
        undoOperations.push({
            action: "delete",
            id: newId,
        });
        if (item.getAttribute("data-type") === "NodeHeading" && item.getAttribute("fold") === "1") {
            foldHeadingIds.push({oldId: item.getAttribute("data-node-id"), newId});
            const responseHTML = await fetchSyncPost("/api/block/getHeadingChildrenDOM", {
                id: item.getAttribute("data-node-id"),
                removeFoldAttr: false,
            });
            if (responseHTML.code !== 0) {
                throw new Error(responseHTML.msg);
            }
            const foldElement = document.createElement("template");
            foldElement.innerHTML = normalizeHTMLAssetIFrameBlockDOM(responseHTML.data);
            let previousID = newId;
            Array.from(foldElement.content.children).slice(1).forEach((childItem: HTMLElement) => {
                childItem.removeAttribute("parent-heading");
                const foldedIDs = new Map<string, string>();
                childItem.querySelectorAll("[data-node-id]").forEach(subItem => {
                    const id = Lute.NewNodeID();
                    foldedIDs.set(subItem.getAttribute("data-node-id"), id);
                    subItem.setAttribute("data-node-id", id);
                    clearBlockElement(subItem);
                });
                const newChildId = Lute.NewNodeID();
                foldedIDs.set(childItem.getAttribute("data-node-id"), newChildId);
                childItem.setAttribute("data-node-id", newChildId);
                clearBlockElement(childItem);
                remapTabsDOMIDs(childItem, foldedIDs);

View on GitHub (pinned to 9f775e8a12)