siyuan-note/siyuan · error
duplicate document ID [%s]
Error message
duplicate document ID [%s]
What it means
Thrown by SetFileTreeSort when the docSorts slice contains two entries with the same document block ID. The function builds a docIDs deduplication map; a repeat ID is rejected before any sort configuration is persisted. This mirrors the notebook deduplication check but for the document level.
Source
Thrown at kernel/model/file.go:2606
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 251596fc0d)
Solutions
- Deduplicate the docSorts array by item.ID on the client side before sending the request.
- Ensure the drag-and-drop handler removes the source document from its old sort position before adding the new entry.
- Inspect the POST /api/filetree/setFileTreeSort payload in the browser network tab to identify the duplicated document ID.
Example fix
// before
payload = { notebookSorts: [], docSorts: [...staleDocs, ...movedDocs] }
// after
const seen = new Set()
payload = {
notebookSorts: [],
docSorts: [...staleDocs, ...movedDocs]
.filter(item => {
if (seen.has(item.id)) return false
seen.add(item.id)
return true
})
} Defensive patterns
Strategy: validation
Validate before calling
// Deduplicate docSorts by ID before calling setFileTreeSort
function dedupeSortItems(items) {
const seen = new Set()
return items.filter(item => {
if (seen.has(item.id)) return false
seen.add(item.id)
return true
})
}
const cleanDocSorts = dedupeSortItems(docSorts) Prevention
- Always deduplicate document sort item arrays by ID before sending.
- Ensure drag-and-drop removes the document from its old position before adding the new entry.
- Use a Map keyed by document ID to build the sort payload, which inherently prevents duplicates.
When it happens
Trigger: Calling POST /api/filetree/setFileTreeSort with a docSorts array where two SortItem elements share the same id field. Typically caused by a UI drag-and-drop handler that appends a new position without removing the old entry for the same document.
Common situations: File tree drag-and-drop where the moved document appears in both its old and new position in the payload; an external API client merging multiple sort sources without deduplication; concurrent edits from two tabs that both include the same document ID.
Related errors
- duplicate notebook ID [%s]
- notebook [%s] not found
- document [%s] not found in opened and unlocked notebooks
- block [%s] is not a sortable document
- assetPath is required for analyze
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/eb054120a3c16473.
Report an issue: GitHub.