siyuan-note/siyuan · error

parse encrypted notebook history conf [%s] failed: %w

Error message

parse encrypted notebook history conf [%s] failed: %w

What it means

After a successful read, isEncryptedHistoryBoxDir unmarshals conf.json into conf.BoxConf; invalid JSON is wrapped as 'parse encrypted notebook history conf [%s] failed'. The library throws it because a malformed BoxConf cannot yield a trustworthy Encrypted flag, and guessing 'false' could expose encrypted recovery material to destructive history operations.

Source

Thrown at kernel/model/crypto.go:954

	if _, err := os.Stat(backupPath); err == nil {
		return true, nil
	} else if !os.IsNotExist(err) {
		return false, fmt.Errorf("stat encrypted notebook history backup [%s] failed: %w", boxDir, err)
	}
	confPath := filepath.Join(siyuanDir, "conf.json")
	if _, err := os.Stat(confPath); err != nil {
		if os.IsNotExist(err) {
			return false, nil
		}
		return false, fmt.Errorf("stat encrypted notebook history conf [%s] failed: %w", boxDir, err)
	}
	data, err := filelock.ReadFile(confPath)
	if err != nil {
		return false, fmt.Errorf("read encrypted notebook history conf [%s] failed: %w", boxDir, err)
	}
	var boxConf conf.BoxConf
	if err = gulu.JSON.UnmarshalJSON(data, &boxConf); err != nil {
		return false, fmt.Errorf("parse encrypted notebook history conf [%s] failed: %w", boxDir, err)
	}
	return boxConf.Encrypted, nil
}

// cachedDEKs 缓存已解锁加密笔记本的 DEK,按 boxID 索引。
// KEK 不全局缓存("严格每笔记本单独解锁"语义):UnlockBox 临时派生 KEK 解出 DEK 后即丢弃 KEK,
// 仅保留 per-box DEK 供后续读写加解密。
var (
	cachedDEKs     = map[string][]byte{}
	cachedDEKsLock sync.RWMutex
)

// boxLastAccess 记录每个加密笔记本最近一次真实用户交互或显式保活时间(unix 纳秒),供自动锁定 cron 使用。
// key: boxID, value: *atomic.Int64。UnlockBox 成功时初始化,Unmount 时清理。
var boxLastAccess sync.Map

// EnableEncryptedNotebookWithSync 在启用前先完成同步;同步恢复了既有配置时只校验原主密码。
func EnableEncryptedNotebookWithSync(password string) error {

View on GitHub (pinned to 8641553a1f)

Solutions

  1. Open the conf named in [%s] and validate its JSON (jq or any JSON linter) to see the exact syntax error
  2. Restore a valid conf.json from the live notebook's .siyuan/conf.json or from backup/sync history
  3. If only the Encrypted flag matters and the file is beyond repair, reconstruct a minimal valid BoxConf JSON with the correct fields
  4. Fix the write path (free disk space, stop killing the process) so future writes are atomic/complete

Example fix

// before: hand-edited conf.json
{ "name": "My notebook", "encrypted": true, }
// after: valid JSON, no trailing comma
{ "name": "My notebook", "encrypted": true }
Defensive patterns

Strategy: validation

Validate before calling

data, err := filelock.ReadFile(confPath)
if err == nil {
    var probe map[string]any
    if err := json.Unmarshal(data, &probe); err != nil { return fmt.Errorf("conf.json invalid: %w", err) }
}

Type guard

func validBoxConfJSON(data []byte) bool { var c conf.BoxConf; return gulu.JSON.UnmarshalJSON(data, &c) == nil }

Try / catch

enc, err := isEncryptedHistoryBoxDir(boxDir)
if err != nil && errors.Is(errors.Unwrap(err), jsonErrKind) {
    return fmt.Errorf("restore valid conf.json for %s from backup before proceeding", boxDir)
}

Prevention

When it happens

Trigger: Any caller of isEncryptedHistoryBoxDir (encryptedNotebookHistoryBoxDirs, hasEncryptedNotebookDeleteHistory, IsEncryptedHistoryPath) processing a conf.json that is truncated, empty, binary garbage, or hand-edited invalid JSON — typically after an interrupted write, disk-full condition, or manual edit.

Common situations: Kernel killed mid-write to conf.json; full disk producing zero-byte files; users editing conf.json with a text editor and breaking JSON syntax; sync conflict merges concatenating two JSON documents.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/72d4b1545cb20b33. Report an issue: GitHub.