siyuan-note/siyuan · error

enable encrypted notebook failed: failed to persist key back

Error message

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

What it means

Thrown by EnableEncryptedNotebook (crypto.go:1055) when saveNotebookCryptoBackup fails while persisting the freshly generated key backup during initial feature enablement. The function rolls back the in-memory NotebookCrypto to its pre-enable snapshot (conf has not been written yet) and returns the wrapped I/O error. This guard exists so that a half-written backup can never leave the system believing encryption is enabled without a recoverable key backup.

Source

Thrown at kernel/model/crypto.go:1055

	}

	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 251596fc0d)

Solutions

  1. Free disk space on the data volume and confirm the data directory is writable by the kernel process, then retry enable.
  2. Check and clear any stale lock on the backup path (dataCryptoBackupPath()) and verify file ownership/permissions.
  3. If the data directory lives on a network mount, move it to a local writable volume or fix the mount's read/write permissions and retry.
  4. Inspect the wrapped error chain (the %w tail names the syscall/path) to pinpoint the exact I/O failure.
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight: confirm the backup path is writable before enable.
func backupPathWritable() error {
    p := dataCryptoBackupPath()
    f, err := os.CreateTemp(filepath.Dir(p), "crypto-backup-probe-*")
    if err != nil {
        return fmt.Errorf("backup dir not writable: %w", err)
    }
    f.Close()
    os.Remove(f.Name())
    return nil
}

Try / catch

if err := model.EnableEncryptedNotebook(password); err != nil {
    if strings.Contains(err.Error(), "failed to persist key backup") {
        // surface the wrapped I/O cause; advise freeing space / fixing permissions
        respond(c, "cannot write key backup: "+err.Error())
        return
    }
    respond(c, err.Error())
}

Prevention

When it happens

Trigger: First-time enable flow: salt generated, KEK derived, verifier encrypted, Conf.NotebookCrypto mutated in memory, then saveNotebookCryptoBackup(kek) returns a non-nil error. The backup write goes to dataCryptoBackupPath(); it fails on disk-full, permission denied, read-only filesystem, path-too-long, or an existing read-only file lock.

Common situations: Data directory on a nearly-full disk, on a network/SMB mount with intermittent write permission, or on read-only media. A leftover lock or a permissions change after a system migration. Antivirus or sync client holding the backup file open exclusively on Windows.

Related errors


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