siyuan-note/siyuan · error

duplicate notebook ID [%s]

Error message

duplicate notebook ID [%s]

What it means

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.

Source

Thrown at kernel/model/file.go:2588

	fileTreeSortLock.Lock()
	defer fileTreeSortLock.Unlock()
	boxes := map[string]*Box{}
	for _, box := range Conf.GetBoxes() {
		boxes[box.ID] = box
	}
	openedBoxes := map[string]*Box{}
	for _, box := range Conf.GetOpenedBoxes() {
		openedBoxes[box.ID] = box
	}

	notebookPlans := make([]*notebookSortPlan, 0, len(notebookSorts))
	notebookIDs := map[string]struct{}{}
	for _, item := range notebookSorts {
		if nil == item {
			return ret, errors.New("notebook sort item must not be nil")
		}
		if _, ok := notebookIDs[item.ID]; ok {
			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, &notebookSortPlan{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)

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Deduplicate the notebookSorts array by item.ID on the client side before sending the request, keeping only the entry with the latest sort value.
  2. 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.
  3. 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.

Example fix

// before
payload = { notebookSorts: [...oldPositions, ...newPositions] }

// after
const seen = new Set()
payload = {
  notebookSorts: [...oldPositions, ...newPositions]
    .filter(item => {
      if (seen.has(item.id)) return false
      seen.add(item.id)
      return true
    })
}
Defensive patterns

Strategy: validation

Validate before calling

// Deduplicate notebookSorts 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 cleanNotebookSorts = dedupeSortItems(notebookSorts)

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/747d0456f2a758ce. Report an issue: GitHub.