{"record":{"id":"747d0456f2a758ce","repo":"siyuan-note/siyuan","slug":"duplicate-notebook-id-s","errorCode":null,"errorMessage":"duplicate notebook ID [%s]","messagePattern":"duplicate notebook ID \\[(.+?)\\]","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/file.go","lineNumber":2588,"sourceCode":"\tfileTreeSortLock.Lock()\n\tdefer fileTreeSortLock.Unlock()\n\tboxes := map[string]*Box{}\n\tfor _, box := range Conf.GetBoxes() {\n\t\tboxes[box.ID] = box\n\t}\n\topenedBoxes := map[string]*Box{}\n\tfor _, box := range Conf.GetOpenedBoxes() {\n\t\topenedBoxes[box.ID] = box\n\t}\n\n\tnotebookPlans := make([]*notebookSortPlan, 0, len(notebookSorts))\n\tnotebookIDs := map[string]struct{}{}\n\tfor _, item := range notebookSorts {\n\t\tif nil == item {\n\t\t\treturn ret, errors.New(\"notebook sort item must not be nil\")\n\t\t}\n\t\tif _, ok := notebookIDs[item.ID]; ok {\n\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)","sourceCodeStart":2570,"sourceCodeEnd":2606,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/file.go#L2570-L2606","documentation":"Thrown by SetFileTreeSort when the notebookSorts slice passed to the API contains two entries with the same ID. The function iterates notebookSorts and builds a deduplication map (notebookIDs); encountering a repeat ID is treated as invalid input and rejected before any sort.json file is written. This protects the on-disk sort configuration from contradictory sort-order assignments for a single notebook.","triggerScenarios":"Calling POST /api/filetree/setFileTreeSort with a notebookSorts array where two SortItem elements share the same id field (e.g. [{id:\"202401010000-a\",sort:1},{id:\"202401010000-a\",sort:2}]). Can also arise from a frontend bug that appends the same dragged notebook twice or fails to remove the original entry after a drag-and-drop reorder.","commonSituations":"Drag-and-drop reorder race in the file tree where the UI sends stale plus new positions for the same notebook; a plugin or external API client that constructs the sort payload by merging two lists without deduplication; concurrent sort operations from two browser tabs that both include the same notebook.","solutions":["Deduplicate the notebookSorts array by item.ID on the client side before sending the request, keeping only the entry with the latest sort value.","If the error originates from a frontend drag-and-drop handler, ensure the source notebook is removed from its old position before inserting it at the new position, so the payload never contains the ID twice.","Inspect the outgoing POST /api/filetree/setFileTreeSort payload in the browser network tab to find which ID is duplicated and trace it back to the UI state that produced it."],"exampleFix":"// before\npayload = { notebookSorts: [...oldPositions, ...newPositions] }\n\n// after\nconst seen = new Set()\npayload = {\n  notebookSorts: [...oldPositions, ...newPositions]\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 notebookSorts 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 cleanNotebookSorts = dedupeSortItems(notebookSorts)","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always deduplicate sort item arrays by ID before sending to the kernel.","In drag-and-drop handlers, remove the source item from its old position before inserting at the new position.","Log the outgoing sort payload during development to catch duplicate IDs early."],"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"}