siyuan-note/siyuan · error

duplicate source ID [ ]

Error message

duplicate source ID [%s]

What it means

Each source document may appear only once in a reorder request. validateReorderArgs tracks seen IDs and fails with this error if the same ID occurs twice. Duplicates would corrupt the computed ordering, so they are rejected before any mutation.

Solutions

  1. Deduplicate sourceIDs (e.g. via a Set) before sending the request
  2. Fix the selection-merging logic that produces overlapping ranges
  3. Replace rather than append when retrying a failed reorder with the same IDs

Example fix

// before
reorderDocs(rawIDs, targetID, "after") // may contain duplicates
// after
const sources = [...new Set(rawIDs)].filter(id => id !== targetID)
reorderDocs(sources, targetID, "after")
Defensive patterns

Strategy: validation

Validate before calling

const sources = [...new Set(sourceIDs)]
if (sources.length !== sourceIDs.length) { /* deduplicated; proceed with sources */ }

Try / catch

try { await api.reorderDocs(src, targetID, pos) } catch (e) { if (String(e).includes("duplicate source ID")) { src = [...new Set(src)]; return retryOnce() } throw e }

Prevention

When it happens

Trigger: sourceIDs built by concatenating selections that overlap (e.g. two multi-select ranges sharing a document); retry logic appending to an existing list instead of replacing it.

Common situations: Plugins merging drag selections across tree nodes without dedup; idempotency bugs where the same batch is accumulated twice; manually authored API payloads with a repeated ID.

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@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/3f9e4714ea0c2961. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/file.go:2754

	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 9f775e8a12)