siyuan-note/siyuan · error

enable encrypted notebook failed: failed to persist key…

Error message

enable encrypted notebook failed: failed to persist key backup: %w

What it means

EnableEncryptedNotebook persists the key backup (saveNotebookCryptoBackup) BEFORE committing the new crypto configuration to conf.json. If writing that backup fails, it rolls back the in-memory NotebookCrypto settings to their previous value and returns "enable encrypted notebook failed: failed to persist key backup" wrapping the underlying write error, so the feature is never left half-enabled with keys that only exist in volatile conf.

Solutions

  1. Check the wrapped underlying error for the actual filesystem cause (permission denied, no space left, etc.) and fix disk space or permissions on the workspace data directory
  2. Ensure the SiYuan process user owns/can write to the workspace and its data subdirectory (chmod/chown)
  3. Retry the enable call once the filesystem is writable; the operation is idempotent when no key domain exists yet
  4. If a stale/partial backup file blocks the write, remove it manually — only safe when no encrypted notebooks exist yet

Example fix

// before
err := model.EnableEncryptedNotebook(password)
// after
if err := model.EnableEncryptedNotebook(password); err != nil {
    if strings.Contains(err.Error(), "failed to persist key backup") {
        // inspect fs permissions / free space on workspacePath/data before retrying
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-check writability of the backup target's directory before enabling
if err := os.MkdirAll(filepath.Dir(model.DataCryptoBackupPath()), 0o755); err != nil { /* abort: cannot write workspace data dir */ }
if err := filelock.WriteFile(model.DataCryptoBackupPath()+".wtest", []byte("ok")); err != nil { /* abort */ }

Type guard

func isBackupPersistFailure(err error) bool {
    return err != nil && strings.Contains(err.Error(), "failed to persist key backup")
}

Try / catch

if err := model.EnableEncryptedNotebook(pwd); err != nil {
    if isBackupPersistFailure(err) { /* fix fs perms/space reported by wrapped cause, then retry */ }
}

Prevention

When it happens

Trigger: Calling EnableEncryptedNotebook when saveNotebookCryptoBackup cannot write the backup file: workspace data directory read-only, disk full, filesystem/permission errors, antivirus locking the file, or filelock contention on the backup path.

Common situations: Running SiYuan from a read-only mount or full disk; workspace moved to a directory the kernel user cannot write; permission changes after an OS update; backup path locked by a backup/sync tool; container with a read-only /data volume.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/49a1565d6960f1a2. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/crypto.go:1077

	}

	Conf.m.Lock()
	previous := *Conf.NotebookCrypto
	Conf.NotebookCrypto.Enabled = true
	Conf.NotebookCrypto.MasterSalt = salt
	Conf.NotebookCrypto.KDFParams = params
	Conf.NotebookCrypto.KEKVerifier = verifierCT
	Conf.NotebookCrypto.VerifierNonce = verifierNonce
	Conf.m.Unlock()

	// 先持久化恢复备份,再提交 conf。此时尚无加密笔记本和历史依赖,任一步失败都不会孤立既有密文。
	if err := saveNotebookCryptoBackup(kek); err != nil {
		// 备份写失败则恢复启用前的内存配置;conf 尚未写入,无需再执行磁盘回滚。
		logging.LogErrorf("save notebook crypto backup failed: %s", err)
		Conf.m.Lock()
		*Conf.NotebookCrypto = previous
		Conf.m.Unlock()
		return fmt.Errorf("enable encrypted notebook failed: failed to persist key backup: %w", err)
	}
	// Conf.Save 内部会加 Conf.m,不能在持锁状态下调用(RWMutex 不可重入)。
	// 即使配置写入失败,已落盘的备份仍可在下次启动时恢复同一套密钥材料。
	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 {

View on GitHub (pinned to 9f775e8a12)