siyuan-note/siyuan · error

unmarshal notebook crypt backup failed: %w

Error message

unmarshal notebook crypt backup failed: %w

What it means

Wrapped error from gulu.JSON.UnmarshalJSON while parsing the notebook crypto backup file. The bytes were read successfully but are not a valid conf.BoxEncryption JSON — hand-edited, truncated, zero-filled (sparsed by sync tools), or written by an incompatible version with a different schema.

Source

Thrown at kernel/model/crypto.go:2493

func readNotebookCryptBackup(boxID string) (*conf.BoxEncryption, error) {
	if !ast.IsNodeIDPattern(boxID) {
		return nil, errors.New("invalid notebook ID")
	}
	backupPath := notebookCryptoBackupPath(boxID)
	if !filelock.IsExist(backupPath) {
		return nil, nil
	}
	return readBoxEncryptionFile(backupPath)
}

func readBoxEncryptionFile(backupPath string) (*conf.BoxEncryption, error) {
	data, err := filelock.ReadFile(backupPath)
	if err != nil {
		return nil, fmt.Errorf("read notebook crypt backup failed: %w", err)
	}
	var crypt conf.BoxEncryption
	if err = gulu.JSON.UnmarshalJSON(data, &crypt); err != nil {
		return nil, fmt.Errorf("unmarshal notebook crypt backup failed: %w", err)
	}
	if err = validateBoxEncryption(&crypt); err != nil {
		return nil, err
	}
	return &crypt, nil
}

// copyAssetDecryptIfEncrypted 把 srcPath 的 asset 复制到 destPath。
// 若 srcPath 在已解锁的加密笔记本下,读密文→解密→写明文到 destPath(导出目录);
// 否则走 filelock.Copy 原路径(字节级复制,密文/明文均可)。
func copyAssetDecryptIfEncrypted(srcPath, destPath string) error {
	if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
		return err
	}

	boxID := ExtractBoxIDFromAssetsPath(srcPath)
	if boxID != "" && IsEncryptedBox(boxID) {
		HoldBoxReadLock(boxID)

View on GitHub (pinned to afa823b6b4)

Solutions

  1. Restore the backup file from a workspace snapshot (history/backup tooling)
  2. Reconstruct it from conf.json: copy the BoxCrypt object of that notebook into .siyuan/notebook-crypto-backup.json with correct shape, then run validateBoxEncryption-compatible unlock
  3. As a last resort, decrypt-and-recreate: unlock with the known-good conf.json copy, remove the broken backup, and run ChangeMasterPassword to regenerate both files
Defensive patterns

Strategy: try-catch

Try / catch

if err != nil {
    if strings.Contains(err.Error(), "unmarshal notebook crypt backup failed") {
        // reconstruct backup from conf.json BoxCrypt or restore from snapshot, then retry unlock

Prevention

When it happens

Trigger: readNotebookCryptBackup on a notebook-crypto-backup.json that was edited manually or corrupted; files truncated by interrupted writes; a workspace synced via a tool that placeholder-izes files.

Common situations: Users hand-editing JSON to 'fix' encryption; cloud-sync placeholder files materialized as zeros; version mismatches after downgrades.

Related errors


AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18). Data as JSON: /api/errors/4b2d0e4fd17d6a9e. Report an issue: GitHub.