{"record":{"id":"07f4fc6f49446008","repo":"siyuan-note/siyuan","slug":"block-not-found","errorCode":null,"errorMessage":"block not found","messagePattern":"block not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/api/block_op.go","lineNumber":51,"sourceCode":"\nfunc buildUpdatedTaskListItemBlockDOM(id, marker string, luteEngine *lute.Lute) (data string, err error) {\n\tblock, err := model.GetBlock(id, nil)\n\tif err != nil {\n\t\treturn \"\", errors.New(\"get block failed: \" + err.Error())\n\t}\n\n\tif \"NodeListItem\" != block.Type {\n\t\treturn \"\", errors.New(\"block is not a list item\")\n\t}\n\n\ttree, err := filesys.LoadTree(block.Box, block.Path, luteEngine)\n\tif err != nil {\n\t\treturn \"\", errors.New(\"load tree failed: \" + err.Error())\n\t}\n\n\tli := treenode.GetNodeInTree(tree, id)\n\tif li == nil {\n\t\treturn \"\", errors.New(\"block not found\")\n\t}\n\n\tif 3 != li.ListData.Typ {\n\t\treturn \"\", errors.New(\"block is not a task list item\")\n\t}\n\n\tif 1 != len(marker) {\n\t\treturn \"\", errors.New(\"task list item marker length should be 1\")\n\t}\n\n\tliMarker := marker[0]\n\tif '[' == liMarker || ']' == liMarker {\n\t\treturn \"\", errors.New(\"task list item marker can not be [ or ]\")\n\t}\n\n\tmarkerNode := li.ChildByType(ast.NodeTaskListItemMarker)\n\tif nil == markerNode {\n\t\treturn \"\", errors.New(\"task list item marker not found\")","sourceCodeStart":33,"sourceCodeEnd":69,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/api/block_op.go#L33-L69","documentation":"SiYuan's task-list-item update API resolves the block by ID within its loaded document tree via treenode.GetNodeInTree. Although model.GetBlock succeeded earlier in buildUpdatedTaskListItemBlockDOM, the node cannot be located in the freshly parsed AST, so the function aborts with \"block not found\". This indicates the SQL index and the on-disk .sy tree are out of sync, or the tree changed between the two lookups.","triggerScenarios":"Calling POST /api/block/updateTaskListItemMarker (or batchUpdateTaskListItemMarker) with an ID that exists in the block index (blocks table) but not in the parsed tree: a stale/deleted block ID, an ID whose document was just deleted or renamed, or an index that has not caught up after a sync.","commonSituations":"Scripts or plugins caching block IDs that were deleted; a sync operation replaced the document; the blocks SQLite index is stale relative to the .sy files on disk; passing an ID from another notebook or an old history snapshot.","solutions":["Verify the block still exists with /api/filetree/getDoc (or /api/query/sql SELECT * FROM blocks WHERE id='...'), then re-fetch a fresh ID before retrying the marker update","Rebuild the index (Settings - About - Rebuild index, or /api/system/reindex) if the blocks table and .sy files disagree","Confirm the document containing the block was not deleted/moved by a concurrent client or sync; re-open the doc to get current IDs","Re-run the update in a retry after a short delay if a sync/index queue was mid-flight"],"exampleFix":"// before: cached id may be stale\nawait fetchPost('/api/block/updateTaskListItemMarker', { id: cachedId, marker: 'x' });\n// after: re-resolve the id from a fresh SQL query first\nconst rows = await fetchPost('/api/query/sql', { stmt: \"SELECT id FROM blocks WHERE content='TODO' AND type='l' LIMIT 1\" });\nawait fetchPost('/api/block/updateTaskListItemMarker', { id: rows[0].id, marker: 'x' });","handlingStrategy":"validation","validationCode":"const rows = await fetchPost('/api/query/sql', { stmt: `SELECT id FROM blocks WHERE id='${id}'` });\nif (!rows.length) throw new Error(`block ${id} no longer exists; re-resolve its ID`);","typeGuard":"const blockExists = (rows) => Array.isArray(rows) && rows.length > 0 && typeof rows[0].id === 'string';","tryCatchPattern":"try {\n  await fetchPost('/api/block/updateTaskListItemMarker', { id, marker });\n} catch (e) {\n  if (e?.msg === 'block not found') {\n    const fresh = await sql(\"SELECT id FROM blocks WHERE type='l' AND subtype='t' LIMIT 1\");\n    if (fresh.length) return retryWith(fresh[0].id);\n  }\n  throw e;\n}","preventionTips":["Never cache block IDs across sessions or syncs; re-query before writes","After sync or bulk edits, wait for indexing to finish before mutating blocks","Validate IDs against the blocks table in batch scripts and skip missing ones"],"tags":["blocks","stale-id","siyuan","task-list"],"backgroundTag":"record-not-found","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}