{"record":{"id":"eb054120a3c16473","repo":"siyuan-note/siyuan","slug":"duplicate-document-id-s","errorCode":null,"errorMessage":"duplicate document ID [%s]","messagePattern":"duplicate document ID \\[(.+?)\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/file.go","lineNumber":2930,"sourceCode":"\t\t\treturn ret, fmt.Errorf(\"duplicate notebook ID [%s]\", item.ID)\n\t\t}\n\t\tnotebookIDs[item.ID] = struct{}{}\n\n\t\tbox := boxes[item.ID]\n\t\tif nil == box {\n\t\t\treturn ret, fmt.Errorf(\"notebook [%s] not found\", item.ID)\n\t\t}\n\t\tnotebookPlans = append(notebookPlans, &notebookSortPlan{item: item, box: box})\n\t}\n\n\tdocPlans := make([]*docSortPlan, 0, len(docSorts))\n\tdocIDs := map[string]struct{}{}\n\tfor _, item := range docSorts {\n\t\tif nil == item {\n\t\t\treturn ret, errors.New(\"document sort item must not be nil\")\n\t\t}\n\t\tif _, ok := docIDs[item.ID]; ok {\n\t\t\treturn ret, fmt.Errorf(\"duplicate document ID [%s]\", item.ID)\n\t\t}\n\t\tdocIDs[item.ID] = struct{}{}\n\n\t\tbt := treenode.GetBlockTree(item.ID)\n\t\tif nil == bt || nil == openedBoxes[bt.BoxID] {\n\t\t\treturn ret, fmt.Errorf(\"document [%s] not found in opened and unlocked notebooks\", item.ID)\n\t\t}\n\t\tif bt.ID != bt.RootID || \"d\" != bt.Type || IsBoxDoc(bt.BoxID, bt.RootID) {\n\t\t\treturn ret, fmt.Errorf(\"block [%s] is not a sortable document\", item.ID)\n\t\t}\n\t\tif nil == boxes[bt.BoxID] {\n\t\t\treturn ret, fmt.Errorf(\"notebook [%s] not found for document [%s]\", bt.BoxID, item.ID)\n\t\t}\n\t\tdocPlans = append(docPlans, &docSortPlan{item: item, boxID: bt.BoxID, parentPath: path.Dir(bt.Path)})\n\t}\n\n\tdocGroups := map[string]*docSortGroup{}\n\tfor _, plan := range docPlans {","sourceCodeStart":2912,"sourceCodeEnd":2948,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/file.go#L2912-L2948","documentation":"Each document sort item's ID is resolved via treenode.GetBlockTree; if the ID has no block-tree record, or its notebook is not in openedBoxes (closed or locked), planning aborts with \"document [%s] not found in opened and unlocked notebooks\". Only documents residing in open, unlocked notebooks may be reordered.","triggerScenarios":"Sorting a document whose .sy was deleted (stale block-tree row not yet purged), whose notebook is closed, or which lives in a locked/encrypted notebook currently unavailable; also sending an ID that never indexed (block tree not refreshed).","commonSituations":"Document deleted in another window while its ID remains in the sort list; notebook closed between list and sort; workspace synced from another machine without reindexing; sorting immediately after import before indexing completes.","solutions":["Reopen/unlock the target notebook, or remove the document from the sort list","Rebuild the index (reindex notebook) to purge stale block-tree rows, then retry","Re-fetch the document list and construct docSorts from currently existing documents only"],"exampleFix":"// before: sort with stale doc IDs\nsortDocs(savedDocIDs)\n// after: verify each ID still resolves\nvalid := []string{}\nfor _, id := range savedDocIDs {\n    if bt := getBlockTree(id); bt != nil && isOpen(bt.BoxID) {\n        valid = append(valid, id)\n    }\n}\nsortDocs(valid)","handlingStrategy":"validation","validationCode":"const bt = await api.getBlockTree(id);\nif (!bt || !(await api.isOpenNotebook(bt.box_id))) throw new Error('doc not sortable: not in opened/unlocked notebook');","typeGuard":"const isResolvableDoc = (bt, openBoxes) => bt != null && openBoxes.has(bt.box_id);","tryCatchPattern":"try { await api.sortDocs(docSorts); } catch (e) { if (String(e).includes('not found in opened and unlocked notebooks')) { await reindexAndRefresh(); retryWithValidOnly(); } else { throw e; } }","preventionTips":["Wait for indexing to complete after import/sync before sorting","Reopen closed notebooks before restoring persisted orders","Purge stale block-tree rows by rebuilding the index"],"tags":["go","blocktree","not-found","stale-state"],"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"}