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, ¬ebookSortPlan{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
- Remove null entries from docSorts before sending (filter(Boolean) in JS or equivalent).
- Fix the code that populates docSorts so failed lookups are skipped rather than pushed as null.
- 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
- Skip failed lookups instead of pushing null into the doc list
- Sanitize arrays loaded from persistent storage (holes become null after JSON round-trip)
- Assert payload shape with a schema check before calling the API
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
- notebook sort item must not be nil
- duplicate notebook ID [%s]
- target ID [%s] is not in the current order
- source ID [%s] is not in the current order
- attribute view custom color must not be null
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/41bdf4c441371a99.
Report an issue: GitHub.