siyuan-note/siyuan · error
encrypted document history has no matching notebook [%s]
Error message
encrypted document history has no matching notebook [%s]
What it means
For a ciphertext history file the kernel extracts histBoxID from the path and checks that a notebook with that ID actually exists (IsEncryptedBox). If no matching encrypted notebook exists, it errors 'encrypted document history has no matching notebook [%s]'. This prevents decrypting history without the owning notebook's context and keys.
Source
Thrown at kernel/model/history.go:188
data, err := filelock.ReadFile(historyPath)
if err != nil {
logging.LogErrorf("read file [%s] failed: %s", historyPath, err)
return
}
// 加密笔记本的历史是密文,按路径里的 boxID 解密后解析
relPath := strings.TrimPrefix(filepath.ToSlash(historyPath), filepath.ToSlash(util.HistoryDir))
relPath = strings.TrimPrefix(relPath, "/")
pathParts := strings.SplitN(relPath, "/", 3)
ciphertext := util.IsCiphertext(data)
if ciphertext {
if len(pathParts) < 3 || !ast.IsNodeIDPattern(pathParts[1]) {
err = errors.New("encrypted document history is missing notebook context")
return
}
histBoxID := pathParts[1]
if !IsEncryptedBox(histBoxID) {
err = fmt.Errorf("encrypted document history has no matching notebook [%s]", histBoxID)
return
}
HoldBoxReadLock(histBoxID)
defer ReleaseBoxReadLock(histBoxID)
dek, dekErr := GetDEKIfUnlocked(histBoxID)
if dekErr != nil {
err = errors.New(Conf.Language(314))
return
}
data, err = DecryptFile(histBoxID, pathParts[2], dek, data)
if err != nil {
logging.LogErrorf("decrypt history [%s] failed: %s", historyPath, err)
return
}
} else if len(pathParts) >= 2 && IsEncryptedBox(pathParts[1]) {
err = fmt.Errorf("encrypted notebook document history is plaintext [%s]", pathParts[1])
return
}View on GitHub (pinned to 8641553a1f)
Solutions
- Recreate the encrypted notebook (or restore its .si folder) with the same notebook ID before opening the history entry
- Copy the history entry back into a workspace where the matching notebook exists
- If history is no longer needed, ignore the entry; it cannot be opened without the notebook's keys
Defensive patterns
Strategy: fallback
Validate before calling
// check notebook still exists before opening its history
const notebooks = (await fetchPost('/api/notebook/lsNotebooks', {})).notebooks;
if (!notebooks.some(n => n.id === boxIDFromHistoryPath)) return null; Try / catch
try {
return await fetchPost('/api/history/getDocHistoryContent', {historyPath});
} catch (e) {
if (String(e.msg).includes('no matching notebook')) {
return {content: null, reason: 'notebook deleted; history not openable'};
} else throw e;
} Prevention
- When deleting an encrypted notebook, expect its history entries to become unreadable
- Keep .si notebook folders intact when restoring backups
- Filter history entries whose notebook no longer exists before rendering the history panel
When it happens
Trigger: Viewing history of an encrypted notebook that has since been deleted, or whose ID changed (notebook recreated, restored from partial backup, sync conflict), so pathParts[1] names a nonexistent notebook.
Common situations: After deleting an encrypted notebook but browsing old history entries; restoring data/ from backup without the .si notebook folder; notebook ID mismatch after manual copying between workspaces.
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
- encrypted document history is missing notebook context
- Conf.Language(314)
- encrypted notebook document history is plaintext [%s]
- Query asset failed [%s]
- Encrypted notebooks do not support this operation
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/58c6923f2bf7cb4d.
Report an issue: GitHub.