siyuan-note/siyuan · error

marshal notebook crypt backup failed: %w

Error message

marshal notebook crypt backup failed: %w

What it means

Wrapped error from gulu.JSON.MarshalIndentJSON while serializing the conf.BoxEncryption struct (WrappedDEK, WrapNonce, KDF params) for the per-notebook backup. Since BoxEncryption is a plain data struct, a marshal failure is essentially unreachable in practice and signals a programming error (unsupported field type added to the struct) rather than an environment problem.

Source

Thrown at kernel/model/crypto.go:2465

	return filepath.Join(util.DataDir, boxID, ".siyuan", notebookCryptoBackupFilename)
}

// writeNotebookCryptBackup 写入加密笔记本的 BoxCrypt 备份。
// 仅在 Encrypted=true 的笔记本上调用,配合 CreateEncryptedBox / ChangeMasterPassword 写入。
func writeNotebookCryptBackup(boxID string, crypt *conf.BoxEncryption) error {
	if !ast.IsNodeIDPattern(boxID) {
		return errors.New("invalid notebook ID")
	}
	if err := validateBoxEncryption(crypt); err != nil {
		return err
	}
	backupPath := notebookCryptoBackupPath(boxID)
	if err := os.MkdirAll(filepath.Dir(backupPath), 0755); err != nil {
		return fmt.Errorf("mkdir notebook crypt backup dir failed: %w", err)
	}
	data, err := gulu.JSON.MarshalIndentJSON(crypt, "", "  ")
	if err != nil {
		return fmt.Errorf("marshal notebook crypt backup failed: %w", err)
	}
	if err := filelock.WriteFile(backupPath, data); err != nil {
		return fmt.Errorf("write notebook crypt backup failed: %w", err)
	}
	return nil
}

// readNotebookCryptBackup 读取加密笔记本的 BoxCrypt 备份。
// 备份文件不存在时返回 (nil, nil),调用方据此区分"非加密笔记本"和"备份不存在"。
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)

View on GitHub (pinned to afa823b6b4)

Solutions

  1. Check the wrapped error for json.UnsupportedTypeError and remove/replace the offending field type in conf.BoxEncryption
  2. Keep BoxEncryption limited to strings, numbers and byte slices
  3. Treat this error as a build-time bug: add a unit test asserting MarshalIndentJSON succeeds on a valid validateBoxEncryption sample
Defensive patterns

Strategy: try-catch

Try / catch

var typeErr *json.UnsupportedTypeError
if errors.As(err, &typeErr) {
    // struct contains an unmarshalable field: fix conf.BoxEncryption

Prevention

When it happens

Trigger: A future field of type chan/func/complex added to conf.BoxEncryption; a custom fork that embeds unmarshalable types in the struct.

Common situations: Almost exclusively hit by developers modifying the struct; not a runtime condition users encounter.

Related errors


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