siyuan-note/siyuan · error
notebook [%s] not found for document [%s]
Error message
notebook [%s] not found for document [%s]
What it means
Thrown by SetFileTreeSort when a document's block tree entry passes the openedBoxes check (the notebook is open) but the notebook is not found in the full boxes map (built from Conf.GetBoxes()). This is a defensive invariant check: openedBoxes is a subset of boxes, so under normal operation this branch is unreachable. If hit, it indicates a race condition where the notebook was deleted or its configuration was removed between building the two maps.
Source
Thrown at kernel/model/file.go:2618
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)})
}
docGroups := map[string]*docSortGroup{}
for _, plan := range docPlans {
group := docGroups[plan.boxID]
if nil == group {
confPath := filepath.Join(util.DataDir, plan.boxID, ".siyuan", "sort.json")
fullSortIDs, readErr := readSortConfMap(confPath)
if readErr != nil {
return ret, readErr
}
group = &docSortGroup{
fullSortIDs: fullSortIDs,
changed: map[string]int{},
parentSorts: map[string]map[string]int{},
}View on GitHub (pinned to 251596fc0d)
Solutions
- Retry the sort operation — if the notebook was being deleted, it should stabilize after the deletion completes.
- If the error persists, check workspace configuration consistency: ensure .siyuan/conf.json lists the notebook consistently.
- As an API client, refresh the notebook list and re-submit the sort payload with only currently valid notebook/document IDs.
Defensive patterns
Strategy: retry
Try / catch
// Retry on the rare race where notebook config is inconsistent mid-operation
async function setFileTreeSortSafe(notebookSorts, docSorts, maxRetries = 2) {
for (let i = 0; i <= maxRetries; i++) {
try {
return await post('/api/filetree/setFileTreeSort', { notebookSorts, docSorts })
} catch (e) {
if (e.message.includes('not found for document') && i < maxRetries) {
await sleep(500)
continue
}
throw e
}
}
} Prevention
- This error indicates a race condition — retry after a brief delay.
- Avoid concurrent notebook deletion and sort operations in the same workspace.
- If the error persists, check workspace configuration consistency.
When it happens
Trigger: An extremely narrow race: the document's notebook is returned by Conf.GetOpenedBoxes() (building openedBoxes) but is missing from Conf.GetBoxes() (building boxes) within the same SetFileTreeSort call. This can happen if a notebook is being deleted concurrently on another goroutine while the sort operation is validating.
Common situations: Concurrent notebook deletion while a sort operation is in flight; a corrupted or inconsistent workspace configuration where GetOpenedBoxes and GetBoxes disagree; very unlikely under normal single-user operation.
Related errors
- notebook [%s] not found
- duplicate notebook ID [%s]
- duplicate document ID [%s]
- document [%s] not found in opened and unlocked notebooks
- block [%s] is not a sortable document
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/41a5589cf41e9854.
Report an issue: GitHub.