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

  1. Refresh the doc tree and re-select the documents, then retry with current IDs
  2. Verify each sourceID via POST /api/filetree/getDoc; drop IDs that no longer resolve
  3. Re-open the source notebook (POST /api/notebook/openNotebook) if it was closed
  4. Refresh the filetree/index (POST /api/filetree/refreshFiletree) to remove stale block-tree entries
  5. 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

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


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/778701562598f6e3. Report an issue: GitHub.