siyuan-note/siyuan · error

failed to persist key backup

Error message

failed to persist key backup: %w

What it means

ImportNotebookCryptoBackup wraps any error from writeNotebookCryptoBackupData as "failed to persist key backup: %w". The backup file is written BEFORE the in-memory Conf is committed (so a failure here leaves Conf unchanged and the import can be retried safely); this error means the validated backup could not be durably written to <DataDir>/.siyuan/data-crypto-backup.json.

Solutions

  1. Check free disk space in the workspace/data partition and free space if needed
  2. Verify write permissions on <DataDir>/.siyuan/ (and file ownership after archive-based restores)
  3. Check whether antivirus or backup software locks the target file, then retry
  4. Retry the import — Conf was not modified, so the operation is safe to repeat
  5. If it persists, inspect the wrapped cause (%w) from the logs for the exact OS error

Example fix

# before
$ ls -l <DataDir>/.siyuan  # owned by root, not writable
# after
$ sudo chown -R $USER <DataDir>/.siyuan
$ # retry the import
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight before import:
fi, err := os.Stat(dataDir + "/.siyuan")
if err != nil || !fi.IsDir() { return errors.New("data dir not writable/missing") }
if err := unix.Access(dataDir+"/.siyuan", unix.W_OK); err != nil { return errors.New("no write permission") }

Type guard

null

Try / catch

if err := ImportNotebookCryptoBackup(raw, password); err != nil {
    var unwrapped error = err
    if strings.Contains(err.Error(), "failed to persist key backup") {
        // safe to retry: Conf was not modified
        log.Printf("persist failed, retry after fixing disk/permissions: %v", unwrapped)
    }
}

Prevention

When it happens

Trigger: ImportNotebookCryptoBackup reaches the persist step after all password/key checks pass, but the underlying mkdir, marshal, or atomicWriteFile of the backup file fails — typically disk full, permission denied on the data directory, or antivirus/file-lock interference on Windows.

Common situations: Full disk during restore on a new device; read-only or permission-restricted data directory (e.g. restored from an archive with wrong ownership); security software holding the target file; failing disk.

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/fbc1a06019402733. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/crypto.go:365

	if !verifyKEKMAC(nc, kek) {
		return errors.New(Conf.Language(317))
	}
	decrypted, dErr := util.DecryptWithAAD(kek, nc.KEKVerifier, []byte("siyuan:kek-verifier"))
	if dErr != nil || string(decrypted) != string(kekVerifierMagic) {
		return errors.New(Conf.Language(311)) // 主密码错误
	}

	// 校验 KEK 能解密现存笔记本和已删除笔记本历史中的 WrappedDEK,避免导入不匹配的备份。
	if !verifyKEKAgainstExistingBoxes(kek, nc) || !verifyKEKAgainstEncryptedHistory(kek, nc) {
		return errors.New(Conf.Language(316)) // 密钥不匹配
	}

	nc.KDFParams = params // 确保写回 Conf 的参数已经通过完整校验。
	nc.Enabled = true

	// 先写 backup,再提交 conf;backup 失败时 conf 尚未改变,可重试
	if err := writeNotebookCryptoBackupData(nc, kek); err != nil {
		return fmt.Errorf("failed to persist key backup: %w", err)
	}
	Conf.m.Lock()
	*Conf.NotebookCrypto = *nc
	Conf.m.Unlock()
	Conf.Save()
	IncSync()
	return nil
}

// saveNotebookCryptoBackup 把当前 NotebookCrypto(含 MasterSalt/KEKVerifier/KDFParams)备份到 DataDir。
// kek 必须非 nil:在 Checksum 定型后计算 KEKMAC 并落盘,保证恢复路径可通过 MAC 校验。
// 无 KEK 生成的备份 KEKMAC 必为空,会被 deriveKEK/恢复路径拒绝,等于制造无法解锁的状态(详见设计 §19)。
func saveNotebookCryptoBackup(kek []byte) error {
	if kek == nil {
		// 无 KEK 时不得生成当前格式备份:KEKMAC 缺失会被 deriveKEK/恢复路径拒绝,
		// 生成即等于制造无法解锁的状态。
		return errors.New("cannot generate notebook crypto backup without KEK")
	}

View on GitHub (pinned to 9f775e8a12)