siyuan-note/siyuan · error
encrypted notebook document history is plaintext [%s]
Error message
encrypted notebook document history is plaintext [%s]
What it means
For an encrypted notebook the kernel requires history files to be ciphertext; a plaintext file under an encrypted notebook's history is an integrity violation (e.g. data written before encryption, tampering, or a broken sync), so the reader refuses with 'encrypted notebook document history is plaintext [%s]' rather than silently returning unauthenticated content.
Source
Thrown at kernel/model/history.go:204
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
}
isLargeDoc = 1024*1024*1 <= len(data)
luteEngine := NewLute()
if err = treenode.CheckSpecJSON(data); nil != err {
return
}
historyTree, err := dataparser.ParseJSONWithoutFix(data, luteEngine.ParseOptions)
if err != nil {
logging.LogErrorf("parse tree from file [%s] failed: %s", historyPath, err)
return
}
id = historyTree.Root.ID
rootID = historyTree.Root.ID
if ciphertext && rootID+".sy" != filepath.Base(historyPath) {
return "", "", "", false, errors.New("encrypted document history root ID does not match its filename")
}View on GitHub (pinned to 8641553a1f)
Solutions
- Remove or ignore the stale plaintext history entries generated before encryption, then regenerate history by editing documents in the encrypted notebook
- Verify sync integrity — re-sync the history data from a trusted source
- Do not manually edit files under data/history/; restore the history directory from backup if it was modified
Defensive patterns
Strategy: try-catch
Try / catch
try {
return await fetchPost('/api/history/getDocHistoryContent', {historyPath});
} catch (e) {
if (String(e.msg).includes('history is plaintext')) {
// mark the entry as corrupt/legacy and exclude it from the history list
return {content: null, corrupt: true};
} else throw e;
} Prevention
- Never edit or inject files under data/history/
- Use SiYuan's own sync; third-party sync of history can produce inconsistent snapshots
- After enabling notebook encryption, treat pre-encryption plaintext history as legacy and regenerate it
When it happens
Trigger: pathParts[1] is an encrypted notebook ID but util.IsCiphertext(data) is false — the history blob for that notebook is plaintext.
Common situations: History generated by an older kernel version before encryption was applied; data corrupted by third-party sync tools writing plaintext snapshots; manual edits under history/; mixing workspaces where the notebook was encrypted only later (old plaintext history remains).
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- encrypted document history is missing notebook context
- encrypted document history has no matching notebook [%s]
- Conf.Language(314)
- 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/9a0d255c3bdecb81.
Report an issue: GitHub.