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

  1. Remove or ignore the stale plaintext history entries generated before encryption, then regenerate history by editing documents in the encrypted notebook
  2. Verify sync integrity — re-sync the history data from a trusted source
  3. 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

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


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