{"record":{"id":"d23d8f782ad32167","repo":"siyuan-note/siyuan","slug":"block-not-found-s-operation","errorCode":null,"errorMessage":"block not found [%s]","messagePattern":"block not found \\[(.+?)\\]","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/block_operation.go","lineNumber":46,"sourceCode":"\t\tflushTx(queued)\n\t}\n\n\tswitch operation.Action {\n\tcase \"appendInsert\", \"insert\":\n\t\tif operation.Action == \"appendInsert\" || operation.PreviousID == \"\" && operation.NextID == \"\" {\n\t\t\tif err = treenode.CheckContainerParent(operation.ParentID); err != nil {\n\t\t\t\treturn nil, err\n\t\t\t}\n\t\t}\n\tcase \"delete\":\n\t\t// 外部删除请求必须命中现存节点，编辑器内部仍可使用幂等删除。\n\t\ttree, loadErr := LoadTreeByBlockID(operation.ID)\n\t\tif loadErr != nil {\n\t\t\treturn nil, loadErr\n\t\t}\n\t\tnode := treenode.GetNodeInTree(tree, operation.ID)\n\t\tif node == nil {\n\t\t\treturn nil, fmt.Errorf(\"block not found [%s]\", operation.ID)\n\t\t}\n\t\tif node == tree.Root {\n\t\t\treturn nil, fmt.Errorf(\"document cannot be deleted as a block [%s]\", operation.ID)\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"unsupported block operation [%s]\", operation.Action)\n\t}\n\n\ttx := &Transaction{DoOperations: []*Operation{operation}}\n\tif err = performTxSyncLocked(tx); err != nil {\n\t\treturn nil, err\n\t}\n\treturn []*Transaction{tx}, nil\n}\n","sourceCodeStart":28,"sourceCodeEnd":61,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/block_operation.go#L28-L61","documentation":"PerformBlockOperation loads the tree for operation.ID and then looks the node up; if the tree loads but no node with that ID exists, it returns this error. It guards the delete-block operation against acting on a nonexistent block and happens before any transaction is created.","triggerScenarios":"PerformBlockOperation with Action=\"delete\" (and a valid ID format) where LoadTreeByBlockID succeeds but the node is absent from the tree — e.g. an ID that equals the root ID handled elsewhere, a removed block, or an ID pointing into a different tree.","commonSituations":"API clients retrying a delete that already succeeded; synced deletions landing between the caller's read and the call; automation passing document root IDs through the generic block-op endpoint.","solutions":["Confirm the block ID still exists via /api/block/getBlockInfo before performing the operation.","Treat this as an already-deleted success case: catch the error and skip re-deleting.","Re-read the block IDs from the document after any sync/delete activity.","Do not route document-level deletions through the block operation API; use /api/filetree/removeDoc."],"exampleFix":"// before\nawait fetchPost('/api/block/performBlockOperation', { operations: [{ action: 'delete', id: id }] });\n// after\nconst info = await fetchPost('/api/block/getBlockInfo', { id });\nif (info.code === 0) {\n  await fetchPost('/api/block/performBlockOperation', { operations: [{ action: 'delete', id }] });\n} // else: already deleted, nothing to do","handlingStrategy":"try-catch","validationCode":"async function canDeleteBlock(id) {\n  const res = await fetchPost('/api/block/getBlockInfo', { id });\n  return res.code === 0 && res.data.type !== 'd';\n}","typeGuard":null,"tryCatchPattern":"try { await performBlockOperation('delete', id); } catch (e) { if (String(e.msg).includes('block not found')) return; /* already gone */ throw e; }","preventionTips":["Verify block existence before delete calls.","Treat 'not found' on delete as success (idempotent deletes).","Keep block ID references short-lived."],"tags":["block-not-found","delete","stale-id","kernel"],"backgroundTag":"entity-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"}