siyuan-note/siyuan · critical

encrypted notebook metadata verification failed after write

Error message

encrypted notebook metadata verification failed after write

What it means

After writing all encrypted state (metadata, conf, backup), the code reads the box conf back and verifies Encrypted is true and BoxCrypt is present. If the read-back does not confirm the state persisted, it raises this explicit error instead of silently treating the notebook as encrypted. It is an internal invariant check guarding against silent write failures (e.g. cache staleness or a failed save that reports success).

Solutions

  1. Re-run the enable-encryption operation; a transient read-back race usually resolves on retry
  2. Check for concurrent access: only one kernel instance should run against the workspace; stop sync clients during conversion
  3. Inspect data/<box>/conf.json manually — if it lacks the encrypted fields, re-attempt the conversion after fixing the storage layer
  4. If reproducible, report as a bug with kernel logs (this path signals an internal invariant violation)

Example fix

// before (external process restoring old conf mid-conversion)
suspend Dropbox/OneDrive sync on the workspace
// after
retry enable-encryption with sync paused -> verification passes
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure single kernel instance and no external writers before converting
const conf = await fetchGet("/api/system/version"); // kernel reachable, single instance assumed
pauseSyncClients();

Try / catch

try {
    await enableNotebookEncryption(boxID, password);
} catch (e) {
    if (String(e.message).includes("verification failed after write")) {
        stopExternalWriters();
        await enableNotebookEncryption(boxID, password); // retry once
        if (stillFailing) reportBugWithKernelLogs();
    }
}

Prevention

When it happens

Trigger: box.GetConf() right after SaveConf returns nil, nil, or a conf with Encrypted=false / BoxCrypt=nil — i.e. the on-disk or cached configuration does not reflect the just-written encrypted state.

Common situations: Filesystem caching or external sync restoring conf.json between write and read-back; a bug in SaveConf silently no-op; concurrent modification of conf.json by another kernel instance or process during conversion.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at kernel/model/crypto.go:2666

	}

	box := &Box{ID: id}
	boxConf := box.GetConf()
	boxConf.Encrypted = true
	boxConf.BoxCrypt = enc
	if err = encryptBoxMetadata(id, boxConf, dek); err != nil {
		return "", fmt.Errorf("encrypt notebook metadata failed: %w", err)
	}
	if err = box.SaveConf(boxConf); err != nil {
		return "", fmt.Errorf("save encrypted notebook conf failed: %w", err)
	}
	if err = writeNotebookCryptBackup(id, enc); err != nil {
		return "", fmt.Errorf("write notebook crypt backup failed: %w", err)
	}
	// 回读校验加密配置已落盘,避免写失败后按普通笔记本处理
	verifyConf := box.GetConf()
	if verifyConf == nil || !verifyConf.Encrypted || verifyConf.BoxCrypt == nil {
		err = errors.New("encrypted notebook metadata verification failed after write")
		return "", err
	}
	markRuntimeEncryptedBox(id)
	invalidateEncryptedPublishAccessCache()

	// 复用刚派生的 DEK 直接开 db + 缓存,省去再次 Argon2id 解锁
	cachedDEKsLock.Lock()
	if err = sql.OpenEncryptedDB(id, dek); err != nil {
		cachedDEKsLock.Unlock()
		return "", err
	}
	if err = treenode.OpenEncryptedBlockTreeDB(id, dek); err != nil {
		sql.CloseEncryptedDB(id)
		cachedDEKsLock.Unlock()
		return "", err
	}
	cachedDEKs[id] = dek
	cachedDEKsLock.Unlock()

View on GitHub (pinned to 8641553a1f)