{"record":{"id":"778701562598f6e3","repo":"siyuan-note/siyuan","slug":"source-document-s-is-unavailable","errorCode":null,"errorMessage":"source document [%s] is unavailable","messagePattern":"source document \\[(.+?)\\] is unavailable","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/file_tree_reorder.go","lineNumber":152,"sourceCode":"\tcurrentIDs := make([]string, 0, len(docs))\n\tfor _, doc := range docs {\n\t\tbyID[doc.ID] = doc\n\t\tcurrentIDs = append(currentIDs, doc.ID)\n\t}\n\tsiblingIDs, err := loadSiblingCustomOrder(box.ID, parentDir, existingSorts)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, id := range siblingIDs {\n\t\tif byID[id] == nil {\n\t\t\treturn nil, fmt.Errorf(\"document [%s] could not be read\", id)\n\t\t}\n\t}\n\tvar fromPaths []string\n\tfor _, id := range sourceIDs {\n\t\tsource := treenode.GetBlockTree(id)\n\t\tif !isSortableDocument(source) || Conf.Box(source.BoxID) == nil {\n\t\t\treturn nil, fmt.Errorf(\"source document [%s] is unavailable\", id)\n\t\t}\n\t\tif source.BoxID == target.BoxID && (target.Path == source.Path ||\n\t\t\tstrings.HasPrefix(target.Path, strings.TrimSuffix(source.Path, \".sy\")+\"/\")) {\n\t\t\treturn nil, fmt.Errorf(\"cannot move document [%s] into itself\", id)\n\t\t}\n\t\tif byID[id] != nil {\n\t\t\tcontinue\n\t\t}\n\t\tsourceParent := path.Dir(source.Path)\n\t\tif sourceParent != \"/\" {\n\t\t\tsourceParent += \".sy\"\n\t\t}\n\t\tsourceDocs, _, loadErr := ListDocTree(source.BoxID, sourceParent, util.SortModeCustom, false, true, int(^uint(0)>>1))\n\t\tif loadErr != nil {\n\t\t\treturn nil, loadErr\n\t\t}\n\t\tfor _, doc := range sourceDocs {\n\t\t\tif doc.ID == id {","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/model/file_tree_reorder.go#L134-L170","documentation":"ReorderDocTree validates each requested source document ID: it must resolve to a sortable document in the block tree and belong to a notebook known to the kernel. If any source fails this check, the entire reorder is rejected with this error. This stops partial or corrupted reorder operations when one of the dragged documents no longer exists.","triggerScenarios":"Calling ReorderDocTree with sourceIDs containing an ID that is missing from the block tree (deleted/moved between selection and reorder), is not a sortable document, or lives in a notebook that is closed (Conf.Box(source.BoxID) == nil). Multi-select drag amplifies this: one stale ID rejects the whole batch.","commonSituations":"Multi-select drag where one selected doc was deleted by sync or another client mid-operation; docs selected in a notebook that got closed; API scripts passing hardcoded/old IDs; selection restored from a stale UI state.","solutions":["Refresh the doc tree and re-select the documents, then retry with current IDs","Verify each sourceID via POST /api/filetree/getDoc; drop IDs that no longer resolve","Re-open the source notebook (POST /api/notebook/openNotebook) if it was closed","Refresh the filetree/index (POST /api/filetree/refreshFiletree) to remove stale block-tree entries","Retry the reorder after sync on all devices has settled"],"exampleFix":"// before (one of several selected docs was deleted elsewhere)\nawait fetchPost('/api/filetree/reorderDocs', {sourceIDs: selection, targetID, position});\n// after (pre-filter to live IDs)\nconst valid = [];\nfor (const id of selection) {\n  const info = await fetchPost('/api/filetree/getDocInfo', {id});\n  if (info.data) valid.push(id);\n}\nif (valid.length) await fetchPost('/api/filetree/reorderDocs', {sourceIDs: valid, targetID, position});","handlingStrategy":"validation","validationCode":"async function filterLiveSources(sourceIds) {\n  const live = [];\n  for (const id of sourceIds) {\n    const r = await fetchPost('/api/filetree/getDocInfo', {id});\n    if (r && r.data && r.data.id === id) live.push(id);\n  }\n  return live;\n}","typeGuard":"function isFullBlockId(v) { return typeof v === 'string' && /^[0-9]{14}-[a-z0-9]{7}$/.test(v); }","tryCatchPattern":"try {\n  await fetchPost('/api/filetree/reorderDocs', {sourceIDs, targetID, position});\n} catch (e) {\n  if (String(e).includes('source document') && String(e).includes('unavailable')) {\n    const live = await filterLiveSources(sourceIDs);\n    if (live.length) await fetchPost('/api/filetree/reorderDocs', {sourceIDs: live, targetID, position});\n  }\n}","preventionTips":["Validate every selected ID before multi-select reorder calls; one stale ID rejects the batch","Keep source notebooks open; closed notebooks make Conf.Box return nil","Refresh the filetree after sync before performing reorder operations","Rebuild selection from live tree data rather than persisted/cached selection state"],"tags":["document","not-found","validation","reorder"],"backgroundTag":"entity-not-found","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}