siyuan-note/siyuan · error
source document [%s] is unavailable
Error message
source document [%s] is unavailable
What it means
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.
Source
Thrown at kernel/model/file_tree_reorder.go:152
currentIDs := make([]string, 0, len(docs))
for _, doc := range docs {
byID[doc.ID] = doc
currentIDs = append(currentIDs, doc.ID)
}
siblingIDs, err := loadSiblingCustomOrder(box.ID, parentDir, existingSorts)
if err != nil {
return nil, err
}
for _, id := range siblingIDs {
if byID[id] == nil {
return nil, fmt.Errorf("document [%s] could not be read", id)
}
}
var fromPaths []string
for _, id := range sourceIDs {
source := treenode.GetBlockTree(id)
if !isSortableDocument(source) || Conf.Box(source.BoxID) == nil {
return nil, fmt.Errorf("source document [%s] is unavailable", id)
}
if source.BoxID == target.BoxID && (target.Path == source.Path ||
strings.HasPrefix(target.Path, strings.TrimSuffix(source.Path, ".sy")+"/")) {
return nil, fmt.Errorf("cannot move document [%s] into itself", id)
}
if byID[id] != nil {
continue
}
sourceParent := path.Dir(source.Path)
if sourceParent != "/" {
sourceParent += ".sy"
}
sourceDocs, _, loadErr := ListDocTree(source.BoxID, sourceParent, util.SortModeCustom, false, true, int(^uint(0)>>1))
if loadErr != nil {
return nil, loadErr
}
for _, doc := range sourceDocs {
if doc.ID == id {View on GitHub (pinned to 8641553a1f)
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
Example fix
// before (one of several selected docs was deleted elsewhere)
await fetchPost('/api/filetree/reorderDocs', {sourceIDs: selection, targetID, position});
// after (pre-filter to live IDs)
const valid = [];
for (const id of selection) {
const info = await fetchPost('/api/filetree/getDocInfo', {id});
if (info.data) valid.push(id);
}
if (valid.length) await fetchPost('/api/filetree/reorderDocs', {sourceIDs: valid, targetID, position}); Defensive patterns
Strategy: validation
Validate before calling
async function filterLiveSources(sourceIds) {
const live = [];
for (const id of sourceIds) {
const r = await fetchPost('/api/filetree/getDocInfo', {id});
if (r && r.data && r.data.id === id) live.push(id);
}
return live;
} Type guard
function isFullBlockId(v) { return typeof v === 'string' && /^[0-9]{14}-[a-z0-9]{7}$/.test(v); } Try / catch
try {
await fetchPost('/api/filetree/reorderDocs', {sourceIDs, targetID, position});
} catch (e) {
if (String(e).includes('source document') && String(e).includes('unavailable')) {
const live = await filterLiveSources(sourceIDs);
if (live.length) await fetchPost('/api/filetree/reorderDocs', {sourceIDs: live, targetID, position});
}
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
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
- target document [%s] is unavailable
- document [%s] could not be read
- Query notebook failed
- document not found or empty
- block not found: %s
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/778701562598f6e3.
Report an issue: GitHub.