siyuan-note/siyuan · error

block [ ] is not a sortable document

Error message

block [%s] is not a sortable document

What it means

The final notebook resolution step in document sort planning: the box owning the resolved block tree must exist in the boxes map (all currently open notebooks). If boxes[bt.BoxID] is nil, planning aborts with "notebook [%s] not found for document [%s]", reporting both the notebook and document IDs. This is a belt-and-braces check distinct from error 1398's openedBoxes check.

Solutions

  1. Reopen the notebook owning the document, then retry the sort
  2. Rebuild the sort payload from a freshly fetched document list so BoxID is current
  3. Avoid concurrent notebook close/move operations while a sort request is in flight

Example fix

// before
sortDocs([{id: docID, pos}])
// after: confirm the owning notebook is open first
const bt = getBlockTree(docID);
const boxes = listNotebooks().map(b => b.id);
if (boxes.includes(bt.box_id)) { sortDocs([{id: docID, pos}]) } else { openNotebook(bt.box_id) }
Defensive patterns

Strategy: validation

Validate before calling

const boxes = (await api.listNotebooks()).data.notebooks.map(n => n.id);
const bt = await api.getBlockTree(docID);
if (!bt || !boxes.includes(bt.box_id)) throw new Error('owning notebook not open');

Type guard

const hasOpenOwner = (bt, boxSet) => bt != null && boxSet.has(bt.box_id);

Try / catch

try { await api.sortDocs(items); } catch (e) { if (String(e).includes('not found for document')) { await openNotebook(extractBoxId(e)); retry(); } else { throw e; } }

Prevention

When it happens

Trigger: The document's block-tree row exists and its notebook appears opened in one map but not the other — e.g. notebook closed concurrently while planning, openedBoxes populated from a different snapshot than boxes, or the document was just moved across notebooks and its BoxID changed.

Common situations: Race between closing a notebook and submitting a sort; moving a document to another notebook in one window while sorting it in another; stale cached box list in a plugin.

Understand the failure class

Background: "Not found" and "does not exist" errors: why "Task not found", "No such folder", and "Can't find" fire when a lookup comes back empty — this error's family across 14 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/4d52619bd5038ff4. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/file.go:2939

	}

	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)})
	}

	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,

View on GitHub (pinned to 9f775e8a12)