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

  1. Retry the sort operation — if the notebook was being deleted, it should stabilize after the deletion completes.
  2. If the error persists, check workspace configuration consistency: ensure .siyuan/conf.json lists the notebook consistently.
  3. 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

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


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/41a5589cf41e9854. Report an issue: GitHub.