siyuan-note/siyuan · error

duplicate source ID [%s]

Error message

duplicate source ID [%s]

What it means

validateReorderArgs rejects repeated IDs within sourceIDs using a seen-set. Each source document should appear exactly once in the move list; duplicates would double-insert the same document in reorderIDSequence and corrupt the resulting order. The error names the first duplicated ID.

Source

Thrown at kernel/model/file.go:2730

	IncSync()
	pushFiletreeSortChanged(sortIDs)
	return
}

func validateReorderArgs(sourceIDs []string, targetID, position string) error {
	if 1 > len(sourceIDs) {
		return errors.New("source IDs must not be empty")
	}
	if "before" != position && "after" != position {
		return fmt.Errorf("invalid reorder position [%s]", position)
	}
	seen := map[string]struct{}{}
	for _, sourceID := range sourceIDs {
		if sourceID == targetID {
			return fmt.Errorf("target ID [%s] must not be included in source IDs", targetID)
		}
		if _, ok := seen[sourceID]; ok {
			return fmt.Errorf("duplicate source ID [%s]", sourceID)
		}
		seen[sourceID] = struct{}{}
	}
	return nil
}

func isSortableDocument(tree *treenode.BlockTree) bool {
	return nil != tree && tree.ID == tree.RootID && "d" == tree.Type && !IsBoxDoc(tree.BoxID, tree.RootID)
}

func loadSiblingCustomOrder(boxID, parentPath string, fullSortIDs map[string]int) (ret []string, err error) {
	absParentPath := filepath.Join(util.DataDir, boxID, parentPath)
	files, err := os.ReadDir(absParentPath)
	if nil != err {
		return nil, fmt.Errorf("read dir [%s] failed: %w", absParentPath, err)
	}
	for _, file := range files {
		if file.IsDir() || !strings.HasSuffix(file.Name(), ".sy") {

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Deduplicate sourceIDs before the call, e.g. via a map or slices.Contains filter
  2. Fix the caller so each document is added to the sources list only once per operation
  3. If merging multiple selection sources, dedupe after the merge

Example fix

// before
err := model.ReorderDocs(sourceIDs, targetID, position) // may contain dupes
// after
seen := map[string]bool{}
unique := sourceIDs[:0]
for _, id := range sourceIDs {
    if !seen[id] {
        seen[id] = true
        unique = append(unique, id)
    }
}
err := model.ReorderDocs(unique, targetID, position)
Defensive patterns

Strategy: validation

Validate before calling

seen := map[string]bool{}
for _, id := range sourceIDs {
    if seen[id] {
        return errors.New("duplicate source ID: " + id)
    }
    seen[id] = true
}

Type guard

func hasNoDuplicates(ids []string) bool {
    seen := make(map[string]struct{}, len(ids))
    for _, id := range ids {
        if _, ok := seen[id]; ok { return false }
        seen[id] = struct{}{}
    }
    return true
}

Try / catch

if !hasNoDuplicates(sourceIDs) {
    sourceIDs = dedupe(sourceIDs) // dedupe before calling
}

Prevention

When it happens

Trigger: Calling ReorderDocs(["doc-a", "doc-a", "doc-b"], targetID, "before"); callers that merge selections from multiple origins without deduplicating (e.g. union of dragged + selected lists); loops that append an ID per event instead of once per document.

Common situations: Multi-select drag implementations that fire once per dragged item and concatenate IDs; bulk automation scripts processing duplicates from a stale index; event handlers accumulating selection state across drags.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/3f9e4714ea0c2961. Report an issue: GitHub.