{"record":{"id":"d70b8b7912c59283","repo":"siyuan-note/siyuan","slug":"block-s-is-not-a-document","errorCode":null,"errorMessage":"block [%s] is not a document","messagePattern":"block \\[(.+?)\\] is not a document","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/blockinfo.go","lineNumber":569,"sourceCode":"\n\tfor _, id := range ids {\n\t\tret[id] = nodesIndexes[id]\n\t}\n\treturn\n}\n\nfunc GetDocBlocksOrders(id string) (ret []string, err error) {\n\tret = []string{}\n\ttree, err := LoadTreeByBlockID(id)\n\tif err != nil {\n\t\treturn\n\t}\n\tif nil == tree || nil == tree.Root {\n\t\terr = ErrTreeNotFound\n\t\treturn\n\t}\n\tif tree.Root.ID != id {\n\t\terr = fmt.Errorf(\"block [%s] is not a document\", id)\n\t\treturn\n\t}\n\n\tret = getDocBlocksOrdersInTree(tree)\n\treturn\n}\n\nfunc getDocBlocksOrdersInTree(tree *parse.Tree) (ret []string) {\n\tret = []string{}\n\tast.Walk(tree.Root, func(n *ast.Node, entering bool) ast.WalkStatus {\n\t\tif !entering || n == tree.Root || !n.IsBlock() || ast.NodeKramdownBlockIAL == n.Type || \"\" == n.ID {\n\t\t\treturn ast.WalkContinue\n\t\t}\n\n\t\tret = append(ret, n.ID)\n\t\treturn ast.WalkContinue\n\t})\n\treturn","sourceCodeStart":551,"sourceCodeEnd":587,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/blockinfo.go#L551-L587","documentation":"In GetDocBlocksOrders (blockinfo.go:558), after the tree for id is loaded and confirmed non-nil, the function checks tree.Root.ID != id and if so returns fmt.Errorf('block [%s] is not a document', id). This walker (getDocBlocksOrdersInTree) returns the ordered list of child block ids under a document root, so it must be given a document (root) id; passing any non-root block id is a caller error, not a data problem. ErrTreeNotFound is returned separately when the tree itself is missing.","triggerScenarios":"POST /api/block/getDocBlocksOrders {id} with the id of a paragraph, heading, list, list-item, embed, or any other non-document block instead of the document root id.","commonSituations":"Caller confuses a block id with its document id; passes a selection/focus id straight through; copies a child id from a ref instead of resolving it to its root.","solutions":["Resolve the id to its document root first via /api/block/getBlockInfo (rootID field) and pass that.","On the client, derive the doc id from the open editor document rather than from the current selection.","If you only have a child id, walk up: childID -> blockInfo.rootID -> getDocBlocksOrders(rootID)."],"exampleFix":"// before\norders, err := model.GetDocBlocksOrders(childID)\n\n// after: resolve to the document root first\nrootID := treenode.GetBlockTree(childID).RootID\norders, err := model.GetDocBlocksOrders(rootID)","handlingStrategy":"validation","validationCode":"// Resolve a child id to its document root before asking for block orders.\nbt := treenode.GetBlockTree(id)\ndocID := id\nif bt != nil { docID = bt.RootID }\nmodel.GetDocBlocksOrders(docID)","typeGuard":null,"tryCatchPattern":"// HTTP caller: on 'is not a document', resolve rootID and retry.\nlet r = await fetchSyncPost('/api/block/getDocBlocksOrders', {id})\nif (r.code === -1 && /not a document/i.test(r.msg)) {\n    const info = await fetchSyncPost('/api/block/getBlockInfo', {id})\n    r = await fetchSyncPost('/api/block/getDocBlocksOrders', {id: info.data.rootID})\n}","preventionTips":["Pass the open document's id, not the current selection's id.","Resolve selection -> rootID once at call time instead of trusting the focus id."],"tags":["blockinfo","validation","document","api"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}