siyuan-note/siyuan · error

cannot disable encrypted notebook feature while encrypted no

Error message

cannot disable encrypted notebook feature while encrypted notebooks exist, remove them first

What it means

Thrown by DisableEncryptedNotebook (crypto.go:1077) as a hard precondition guard: the encrypted-notebook feature cannot be turned off while at least one encrypted notebook still exists. Disabling would clear the global MasterSalt/KEKVerifier and delete the key backup, which would permanently lock all remaining encrypted notebooks. The user must remove (or decrypt) every encrypted notebook first.

Source

Thrown at kernel/model/crypto.go:1077

	Conf.Save()
	IncSync()
	return nil
}

// DisableEncryptedNotebook 关闭加密笔记本功能。前置:不能有加密笔记本存在,
// 且不能有依赖当前密钥备份的已删除笔记本历史(否则禁用并删除备份会让这些历史永久锁死,违反 §19)。
// 清除全局加密配置(MasterSalt/KEKVerifier),KEK/DEK 不再可用。
func DisableEncryptedNotebook() error {
	notebookCryptoMu.Lock()
	defer notebookCryptoMu.Unlock()

	// 检查是否还有加密笔记本(含 conf 损坏但存在备份的)
	ids, listErr := listAllEncryptedBoxIDs()
	if listErr != nil {
		return fmt.Errorf("list encrypted notebooks failed: %w", listErr)
	}
	if len(ids) > 0 {
		return errors.New("cannot disable encrypted notebook feature while encrypted notebooks exist, remove them first")
	}
	// 检查历史目录中是否存在已删除加密笔记本的历史快照:其恢复仍依赖当前 MasterSalt/KEKVerifier,
	// 删除备份前必须先清除这些历史(详见设计 §19)
	hasHistory, historyErr := scanEncryptedNotebookHistory()
	if historyErr != nil {
		return fmt.Errorf("check encrypted notebook history failed: %w", historyErr)
	}
	if hasHistory {
		return errors.New(Conf.Language(323))
	}

	Conf.m.Lock()
	Conf.NotebookCrypto.Enabled = false
	Conf.NotebookCrypto.MasterSalt = nil
	Conf.NotebookCrypto.KEKVerifier = nil
	Conf.NotebookCrypto.VerifierNonce = nil
	Conf.m.Unlock()

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Remove every encrypted notebook first (delete or convert to normal), then call DisableEncryptedNotebook again.
  2. If a damaged notebook cannot be removed through the UI, locate it via listAllEncryptedBoxIDs error context and delete its directory and per-notebook backup manually after confirming the data is expendable.
  3. Re-run disable only after listAllEncryptedBoxIDs returns an empty set.
Defensive patterns

Strategy: validation

Validate before calling

// Before disable, confirm no encrypted notebooks remain.
ids, err := model.ListAllEncryptedBoxIDs()
if err != nil {
    return err
}
if len(ids) > 0 {
    return fmt.Errorf("remove encrypted notebooks first: %v", ids)
}
// safe to disable

Try / catch

if err := model.DisableEncryptedNotebook(); err != nil {
    if strings.Contains(err.Error(), "encrypted notebooks exist, remove them first") {
        respond(c, "remove all encrypted notebooks before disabling")
        return
    }
    respond(c, err.Error())
}

Prevention

When it happens

Trigger: POST /api/notebook/disableEncryptedNotebooks when listAllEncryptedBoxIDs returns len(ids) > 0. The scan detects encrypted notebooks via conf.json flags, per-notebook key backups, on-disk ciphertext markers, or runtime identity, so even a notebook whose conf is damaged but has a backup counts.

Common situations: User clicks 'disable encrypted notebooks' in settings before removing their encrypted notebook(s). An encrypted notebook whose conf.json was corrupted but whose backup still marks it encrypted, so it is still counted.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/75dbdc88657b96d8. Report an issue: GitHub.