siyuan-note/siyuan · error

document sort item must not be nil

Error message

document sort item must not be nil

What it means

SetFileTreeSort iterates the docSorts array and rejects a nil element with this error before any document is moved. A null entry has no ID or sort weight, so the request is considered malformed and the entire batch sort fails without side effects.

Source

Thrown at kernel/model/file.go:2903

			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)
		}
		docIDs[item.ID] = struct{}{}

		bt := treenode.GetBlockTree(item.ID)
		if nil == bt || nil == openedBoxes[bt.BoxID] {
			return ret, fmt.Errorf("document [%s] not found in opened and unlocked notebooks", item.ID)
		}
		if bt.ID != bt.RootID || "d" != bt.Type || IsBoxDoc(bt.BoxID, bt.RootID) {
			return ret, fmt.Errorf("block [%s] is not a sortable document", item.ID)
		}
		if nil == boxes[bt.BoxID] {
			return ret, fmt.Errorf("notebook [%s] not found for document [%s]", bt.BoxID, item.ID)
		}
		docPlans = append(docPlans, &docSortPlan{item: item, boxID: bt.BoxID, parentPath: path.Dir(bt.Path)})
	}

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Remove null entries from docSorts before sending (filter(Boolean) in JS or equivalent).
  2. Fix the code that populates docSorts so failed lookups are skipped rather than pushed as null.
  3. Validate the payload shape client-side (every item is an object with id and sort) before issuing the request.

Example fix

// before
const docs = allDocs.map(d => lookup(d)); // lookup may return null
fetchPost("/api/filetree/setFileTreeSort", {docSorts: docs});
// after
const docs = allDocs.map(d => lookup(d)).filter(Boolean);
fetchPost("/api/filetree/setFileTreeSort", {docSorts: docs});
Defensive patterns

Strategy: validation

Validate before calling

if (docSorts.some(i => i == null)) {
  throw new Error("docSorts contains null entries");
}

Type guard

const isValidDocItem = (i) => i != null && typeof i.id === "string" && /^[0-9]{14}-[a-z0-9]{7}$/.test(i.id) && typeof i.sort === "number";

Try / catch

try {
  await api.setFileTreeSort({docSorts});
} catch (e) {
  if (String(e).includes("must not be nil")) {
    return api.setFileTreeSort({docSorts: docSorts.filter(Boolean)});
  }
  throw e;
}

Prevention

When it happens

Trigger: Posting a docSorts array containing a JSON null element to the file-tree sort API, typically from a client that appends undefined entries or serializes a sparse array.

Common situations: Plugin builds the doc list with holes (delete leaving undefined in JS arrays); a failed lookup returns null and is pushed into the array unconditionally; JSON produced from Map-to-array conversion with missing keys.

Related errors


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