{"record":{"id":"4d52619bd5038ff4","repo":"siyuan-note/siyuan","slug":"block-s-is-not-a-sortable-document","errorCode":null,"errorMessage":"block [%s] is not a sortable document","messagePattern":"block \\[(.+?)\\] is not a sortable document","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/file.go","lineNumber":2939,"sourceCode":"\t}\n\n\tdocPlans := make([]*docSortPlan, 0, len(docSorts))\n\tdocIDs := map[string]struct{}{}\n\tfor _, item := range docSorts {\n\t\tif nil == item {\n\t\t\treturn ret, errors.New(\"document sort item must not be nil\")\n\t\t}\n\t\tif _, ok := docIDs[item.ID]; ok {\n\t\t\treturn ret, fmt.Errorf(\"duplicate document ID [%s]\", item.ID)\n\t\t}\n\t\tdocIDs[item.ID] = struct{}{}\n\n\t\tbt := treenode.GetBlockTree(item.ID)\n\t\tif nil == bt || nil == openedBoxes[bt.BoxID] {\n\t\t\treturn ret, fmt.Errorf(\"document [%s] not found in opened and unlocked notebooks\", item.ID)\n\t\t}\n\t\tif bt.ID != bt.RootID || \"d\" != bt.Type || IsBoxDoc(bt.BoxID, bt.RootID) {\n\t\t\treturn ret, fmt.Errorf(\"block [%s] is not a sortable document\", item.ID)\n\t\t}\n\t\tif nil == boxes[bt.BoxID] {\n\t\t\treturn ret, fmt.Errorf(\"notebook [%s] not found for document [%s]\", bt.BoxID, item.ID)\n\t\t}\n\t\tdocPlans = append(docPlans, &docSortPlan{item: item, boxID: bt.BoxID, parentPath: path.Dir(bt.Path)})\n\t}\n\n\tdocGroups := map[string]*docSortGroup{}\n\tfor _, plan := range docPlans {\n\t\tgroup := docGroups[plan.boxID]\n\t\tif nil == group {\n\t\t\tconfPath := filepath.Join(util.DataDir, plan.boxID, \".siyuan\", \"sort.json\")\n\t\t\tfullSortIDs, readErr := readSortConfMap(confPath)\n\t\t\tif readErr != nil {\n\t\t\t\treturn ret, readErr\n\t\t\t}\n\t\t\tgroup = &docSortGroup{\n\t\t\t\tfullSortIDs: fullSortIDs,","sourceCodeStart":2921,"sourceCodeEnd":2957,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/file.go#L2921-L2957","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Reopen the notebook owning the document, then retry the sort","Rebuild the sort payload from a freshly fetched document list so BoxID is current","Avoid concurrent notebook close/move operations while a sort request is in flight"],"exampleFix":"// before\nsortDocs([{id: docID, pos}])\n// after: confirm the owning notebook is open first\nconst bt = getBlockTree(docID);\nconst boxes = listNotebooks().map(b => b.id);\nif (boxes.includes(bt.box_id)) { sortDocs([{id: docID, pos}]) } else { openNotebook(bt.box_id) }","handlingStrategy":"validation","validationCode":"const boxes = (await api.listNotebooks()).data.notebooks.map(n => n.id);\nconst bt = await api.getBlockTree(docID);\nif (!bt || !boxes.includes(bt.box_id)) throw new Error('owning notebook not open');","typeGuard":"const hasOpenOwner = (bt, boxSet) => bt != null && boxSet.has(bt.box_id);","tryCatchPattern":"try { await api.sortDocs(items); } catch (e) { if (String(e).includes('not found for document')) { await openNotebook(extractBoxId(e)); retry(); } else { throw e; } }","preventionTips":["Serialize notebook close/move operations with sort requests","Refresh the box list immediately before sorting","Handle cross-notebook moves by re-resolving BoxID before further operations"],"tags":["go","notebook","race-condition","not-found"],"backgroundTag":"entity-not-found","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}