siyuan-note/siyuan · error
hpath refresh crossed an encrypted notebook boundary
Error message
hpath refresh crossed an encrypted notebook boundary
What it means
refreshHPathsTask resolves the task's root document first from the task's box, and for non-encrypted boxes falls back to the global block tree. If the global lookup finds the document in an encrypted notebook while the task was issued against a non-encrypted box (or vice versa context mismatch), it means the hpath refresh would move data across an encrypted/plain notebook boundary — which is forbidden because the two notebooks use different storage (encryption) formats.
Solutions
- Rebuild the block-tree index so the task's document resolves to the correct box
- Verify the notebook encryption status of both task.Box and the tree record's BoxID (IsEncryptedBox) and fix the mismatched flag
- Delete the stale hpath refresh task so it is not retried against the wrong notebook
- If the document was genuinely moved across notebooks, re-trigger the operation through the normal move API rather than the queued refresh
Defensive patterns
Strategy: validation
Validate before calling
if root := treenode.GetBlockTree(id); root != nil && model.IsEncryptedBox(root.BoxID) != model.IsEncryptedBox(taskBox) { /* rebuild index before refreshing */ } Try / catch
ok, err := task.refresh(); if err != nil && err.Error() == "hpath refresh crossed an encrypted notebook boundary" { // rebuild the block-tree index and clear the stale task } Prevention
- Rebuild the block-tree index after changing a notebook's encryption status
- Never move documents manually between encrypted and plain notebooks on disk
- Keep task queues and index rebuilds serialized
When it happens
Trigger: A queued hpath refresh task whose task.Box does not match the block-tree record's BoxID, where the tree record's box is an encrypted notebook while the task box is not (root == nil for task.Box, fallback GetBlockTree finds an encrypted root).
Common situations: Block-tree index stale after a notebook was switched to/from encrypted; a task referencing a document that was moved between notebooks; index rebuild race leaving task.Box out of sync.
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
- hpath refresh document is not indexed
- 26
- Access to encrypted notebook data is not supported via this…
- accessing assets in encrypted notebook
- Argon2id KeyLength must be 32
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/dee4ddb828a95b71.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/hpath_refresh.go:407
return
}
sql.ClearCache()
util.BroadcastByType("main", "databaseIndexCommit", 0, "", map[string]any{"rootIDs": []string{task.ID}, "backlinkChanged": true, "backlinkFull": true})
if !task.started.IsZero() && time.Since(task.started) > time.Second {
logging.LogInfof("refreshed document hpaths [%s], docs [%d], batches [%d], elapsed [%dms], active [%dms]", key, len(task.docs), task.batches, time.Since(task.started).Milliseconds(), task.active.Milliseconds())
}
}
}
func refreshHPathsTask(task *hpathRefreshTask) (done bool, err error) {
if task.started.IsZero() {
task.started = time.Now()
}
root := treenode.GetBlockTreeInBox(task.ID, task.Box)
if !IsEncryptedBox(task.Box) && (root == nil || root.BoxID != task.Box) {
root = treenode.GetBlockTree(task.ID)
if root != nil && IsEncryptedBox(root.BoxID) {
return false, errors.New("hpath refresh crossed an encrypted notebook boundary")
}
}
if root == nil {
// 索引尚未恢复时不能丢弃磁盘上仍存在的文档任务。
if _, statErr := os.Stat(filepath.Join(util.DataDir, task.Box, task.Path)); statErr == nil {
return false, errors.New("hpath refresh document is not indexed")
} else if !os.IsNotExist(statErr) {
return false, statErr
}
return true, nil
}
scope := sha256.Sum256([]byte(root.BoxID + "\x00" + root.Path + "\x00" + root.HPath))
if task.scope != scope {
task.docs, task.index, task.covers = nil, 0, nil
task.blockAfter, task.treeAfter = 0, 0
task.scope = scope
}
if task.docs == nil {View on GitHub (pinned to 9f775e8a12)