{"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":2606,"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":2588,"sourceCodeEnd":2624,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/file.go#L2588-L2624","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\npayload = { notebookSorts: [], docSorts: [...staleDocs, ...movedDocs] }\n\n// after\nconst seen = new Set()\npayload = {\n  notebookSorts: [],\n  docSorts: [...staleDocs, ...movedDocs]\n    .filter(item => {\n      if (seen.has(item.id)) return false\n      seen.add(item.id)\n      return true\n    })\n}","handlingStrategy":"validation","validationCode":"// Deduplicate docSorts by ID before calling setFileTreeSort\nfunction dedupeSortItems(items) {\n  const seen = new Set()\n  return items.filter(item => {\n    if (seen.has(item.id)) return false\n    seen.add(item.id)\n    return true\n  })\n}\nconst cleanDocSorts = dedupeSortItems(docSorts)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["validation","filetree","sort","input-validation"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}