siyuan-note/siyuan · error
%w: %v
Error message
%w: %v
What it means
deriveKEK wraps errMasterPasswordMigrationPending (with the underlying cause via %w/%v) when a master-password migration is still in progress. During migration, the KEK is only accepted after all notebooks and encrypted history have been re-wrapped and a new authenticated global crypto backup can be saved. If saving that backup fails, or the re-wrap verification fails, derivation aborts because the old/new password transition is not yet complete.
Solutions
- Complete the pending master-password migration: call ChangeMasterPassword again with the correct password so all notebooks/history are re-wrapped and the migration marker is removed
- Fix the underlying cause in the wrapped error (%v part): free disk space or repair permissions for the workspace crypto backup path
- If verification keeps failing, ensure every encrypted notebook is present and synced; a missing notebook prevents migration completion
- As last resort restore the workspace from a backup taken before the interrupted migration
Example fix
// before
kek, err := deriveKEK(password)
if errors.Is(err, model.ErrMasterPasswordMigrationPending) {
return err // treated as wrong password, user stuck
}
// after
if errors.Is(err, model.ErrMasterPasswordMigrationPending) {
// retry ChangeMasterPassword/verify with the same password to finish migration,
// surface the %v cause to the user for actionable feedback
return fmt.Errorf("migration pending: %w", err)
} Defensive patterns
Strategy: try-catch
Validate before calling
if model.IsMasterPasswordMigrationPending() {
return errors.New("complete pending master-password migration before unlocking")
} Try / catch
kek, err := model.DeriveKEKForTest(password)
if err != nil {
if errors.Is(err, model.ErrMasterPasswordMigrationPending) {
// surface cause via errors.Unwrap and retry ChangeMasterPassword
}
} Prevention
- Avoid killing the process during ChangeMasterPassword
- Monitor errors.Unwrap(err) for the underlying backup-write cause
- Ensure workspace disk has free space before password changes
- Keep all encrypted notebooks synced before migrating passwords
When it happens
Trigger: Calling UnlockBox/UnlockAndMountBox or ChangeMasterPassword (and test paths) with the new password while a master-password migration marker exists and either decryptHistoryKEKs/verifyKEKAgainst* fails (returns bare errMasterPasswordMigrationPending) or saveNotebookCryptoBackup returns a write error (returns wrapped '%w: %v').
Common situations: A previous ChangeMasterPassword was interrupted (crash/power loss) leaving the migration marker on disk; the workspace backup directory is not writable or on a full disk; synced machines have partially migrated WrappedDEKs so verification against all boxes fails.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Conf.Language(316) + " [box=" + id + "]"
- master password migration is pending
- : (errMasterPasswordMigrationPending; Conf.Language(320)…
- accessing assets in encrypted notebook
- attribute view spec is too new
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/2c4efe4160fee529.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/crypto.go:1310
logging.LogInfof("repaired notebook crypto configuration from authenticated backup")
} else if !backupAuthenticated {
// 同步备份可能属于另一轮完整改密;只要本地配置仍与全部笔记本一致,就继续使用本地配置,
// 不覆盖候选备份,等待其余 WrappedDEK 同步完成后由新密码采用。
logging.LogWarnf("notebook crypto backup differs from usable local configuration; keeping both candidates")
}
}
if migrationPending {
// 崩溃恢复后的首次新密码验证:确认所有笔记本都已切换到新 KEK,再生成带认证的全局备份并结束迁移。
keys, keyErr := decryptHistoryKEKs(kek, nc.HistoryKEKs)
clearHistoryKEKs(keys)
if keyErr != nil || !verifyKEKAgainstExistingBoxes(kek, nil) || !verifyKEKAgainstEncryptedHistory(kek, &nc) {
zeroAndClear(kek)
return nil, errMasterPasswordMigrationPending
}
if err = saveNotebookCryptoBackup(kek); err != nil {
zeroAndClear(kek)
return nil, fmt.Errorf("%w: %v", errMasterPasswordMigrationPending, err)
}
removeMasterPasswordMigration()
}
return kek, nil
}
// decryptBoxCrypt 用 KEK 解密 box 的 WrappedDEK。优先使用 GetBoxEncryption 的结果(conf → backup fallback),
// 若解密失败则尝试 backup 中不同的 WrappedDEK。
// 返回解密后的 DEK 和实际使用的 BoxCrypt(可能来自 backup)。
// 若 backup 被使用会自动修复 conf.json 和刷新 backup。
func decryptBoxCrypt(boxID string, kek []byte) (dek []byte, boxCrypt *conf.BoxEncryption, err error) {
boxCrypt, err = GetBoxEncryption(boxID)
if err != nil || boxCrypt == nil || len(boxCrypt.WrappedDEK) == 0 {
return nil, nil, fmt.Errorf("no encrypted key material for box [%s]", boxID)
}
nc := currentNotebookCrypto()
dek, err = decryptWrappedDEKWithHistory(boxID, boxCrypt, kek, nc)View on GitHub (pinned to 9f775e8a12)