siyuan-note/siyuan · warning

document changed before hpath refresh completed

Error message

document changed before hpath refresh completed

What it means

After processing all queued sub-document steps of an hpath refresh, refreshHPathsTask re-reads the root block-tree record and compares box, path and hpath with the root snapshot taken when the task started. Any difference means the document changed concurrently while the refresh was running, so applying the remaining work would corrupt the path structure and the task is failed.

Solutions

  1. Simply retry: the task is marked failed and a new refresh task will be generated for the new hpath — retrigger the rename or run the hpath refresh job again
  2. Avoid issuing concurrent renames/moves on the same document subtree while a refresh is in progress
  3. If caused by sync, let sync finish before performing renames, or re-run refresh after sync completes
  4. Check for automation scripts racing the UI and serialize rename operations
Defensive patterns

Strategy: retry

Try / catch

ok, err := task.refresh(); if err != nil && err.Error() == "document changed before hpath refresh completed" { // retrigger the refresh for the document's current hpath }

Prevention

When it happens

Trigger: A rename/move/edit of the root document (changing its BoxID, Path, or HPath) happens between the start of refreshHPathsTask and completion of task.docs processing — e.g. a user renames a parent while the background refresh job is still walking its descendants.

Common situations: Rapid successive renames of parent documents; concurrent API scripts renaming while the UI also modifies the tree; sync applying remote tree changes during a local refresh.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at kernel/model/hpath_refresh.go:474

		for _, doc := range docs {
			task.docs = append(task.docs, doc.ID)
		}
		task.covers = map[string]*hpathRefreshTask{}
		for key, other := range hpathRefresh.tasks {
			if other == task || other.Box != root.BoxID {
				continue
			}
			bt := treenode.GetBlockTreeInBox(other.ID, other.Box)
			if bt != nil && bt.BoxID == root.BoxID && strings.HasPrefix(bt.Path, strings.TrimSuffix(root.Path, ".sy")+"/") {
				task.covers[key] = other
				other.coveredBy = task
			}
		}
	}
	if task.index >= len(task.docs) {
		current := treenode.GetBlockTreeInBox(root.ID, root.BoxID)
		if current == nil || current.BoxID != root.BoxID || current.Path != root.Path || current.HPath != root.HPath {
			return false, errors.New("document changed before hpath refresh completed")
		}
		return true, nil
	}
	doc := treenode.GetBlockTreeInBox(task.docs[task.index], root.BoxID)
	if doc == nil || doc.BoxID != root.BoxID || (doc.Path != root.Path && !strings.HasPrefix(doc.Path, strings.TrimSuffix(root.Path, ".sy")+"/")) {
		task.index++
		task.blockAfter, task.treeAfter = 0, 0
		return false, nil
	}
	fingerprint := sha256.Sum256([]byte(doc.BoxID + "\x00" + doc.Path + "\x00" + doc.HPath))
	if task.fingerprint != fingerprint {
		task.blockAfter, task.treeAfter = 0, 0
		task.fingerprint = fingerprint
	}
	start := time.Now()
	blockAfter, treeAfter, docDone, busy, err := sql.RefreshHPathsBatch(doc, task.blockAfter, task.treeAfter, task.limit)
	if err != nil || busy {
		return false, err

View on GitHub (pinned to 9f775e8a12)