siyuan-note/siyuan · error
duplicate document ID
Error message
duplicate document ID [%s]
What it means
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.
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
Example fix
// before: sort with stale doc IDs
sortDocs(savedDocIDs)
// after: verify each ID still resolves
valid := []string{}
for _, id := range savedDocIDs {
if bt := getBlockTree(id); bt != nil && isOpen(bt.BoxID) {
valid = append(valid, id)
}
}
sortDocs(valid) Defensive patterns
Strategy: validation
Validate before calling
const bt = await api.getBlockTree(id);
if (!bt || !(await api.isOpenNotebook(bt.box_id))) throw new Error('doc not sortable: not in opened/unlocked notebook'); Type guard
const isResolvableDoc = (bt, openBoxes) => bt != null && openBoxes.has(bt.box_id);
Try / catch
try { await api.sortDocs(docSorts); } catch (e) { if (String(e).includes('not found in opened and unlocked notebooks')) { await reindexAndRefresh(); retryWithValidOnly(); } else { throw e; } } Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.
Related errors
- notebook [ ] not found
- attribute view drop target changed; retry the drag
- attribute view groups changed; retry the drag
- attribute view [ ] not found in box [ ]
- block [ ] is not a sortable document
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/eb054120a3c16473.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/file.go:2930
return ret, fmt.Errorf("duplicate notebook ID [%s]", item.ID)
}
notebookIDs[item.ID] = struct{}{}
box := boxes[item.ID]
if nil == box {
return ret, fmt.Errorf("notebook [%s] not found", item.ID)
}
notebookPlans = append(notebookPlans, ¬ebookSortPlan{item: item, box: box})
}
docPlans := make([]*docSortPlan, 0, len(docSorts))
docIDs := map[string]struct{}{}
for _, item := range docSorts {
if nil == item {
return ret, errors.New("document sort item must not be nil")
}
if _, ok := docIDs[item.ID]; ok {
return ret, fmt.Errorf("duplicate document ID [%s]", item.ID)
}
docIDs[item.ID] = struct{}{}
bt := treenode.GetBlockTree(item.ID)
if nil == bt || nil == openedBoxes[bt.BoxID] {
return ret, fmt.Errorf("document [%s] not found in opened and unlocked notebooks", item.ID)
}
if bt.ID != bt.RootID || "d" != bt.Type || IsBoxDoc(bt.BoxID, bt.RootID) {
return ret, fmt.Errorf("block [%s] is not a sortable document", item.ID)
}
if nil == boxes[bt.BoxID] {
return ret, fmt.Errorf("notebook [%s] not found for document [%s]", bt.BoxID, item.ID)
}
docPlans = append(docPlans, &docSortPlan{item: item, boxID: bt.BoxID, parentPath: path.Dir(bt.Path)})
}
docGroups := map[string]*docSortGroup{}
for _, plan := range docPlans {View on GitHub (pinned to 9f775e8a12)