siyuan-note/siyuan · error

failed to persist key backup: %w

Error message

failed to persist key backup: %w

What it means

ImportNotebookCryptoBackup returns a wrapped error at line 358 when writeNotebookCryptoBackupData fails while persisting the validated backup to <DataDir>/.siyuan/data-crypto-backup.json. By this point every cryptographic check passed and the in-memory nc was prepared; the failure is purely I/O (mkdir, marshal, or atomic write). The error wraps the underlying cause with %w so callers can inspect it.

Source

Thrown at kernel/model/crypto.go:358

	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) || !verifyKEKAgainstEncryptedHistory(kek) {
		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 251596fc0d)

Solutions

  1. Inspect the wrapped underlying error (errors.Unwrap / errors.Is) to identify mkdir vs write vs marshal.
  2. Free disk space and verify write permissions on <DataDir>/.siyuan/.
  3. Retry the import once the filesystem issue is resolved — conf has not been mutated yet at this point (the comment notes conf is unchanged on backup failure, so retry is safe).
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure DataDir is writable before importing.
dir := filepath.Join(util.DataDir, ".siyuan")
if info, err := os.Stat(dir); err != nil || !info.IsDir() {
    return errors.New("data dir not available")
}

Try / catch

// Unwrap to distinguish mkdir vs write vs marshal failures.
if err := model.ImportNotebookCryptoBackup(data, password); err != nil {
    var inner error
    if errors.As(err, &inner) {
        log.Printf("persist failed: %v", inner)
    }
}

Prevention

When it happens

Trigger: writeNotebookCryptoBackupData returns an error: the backup directory cannot be created (os.MkdirAll fails), the JSON marshal fails, or atomicWriteFile fails. Causes include insufficient permissions on DataDir, read-only filesystem, disk full, or path resolution failure.

Common situations: DataDir on a read-only or full volume; permission/ownership change on the workspace; antivirus/lock preventing the temp write; removable media ejected mid-operation.

Related errors


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