siyuan-note/siyuan · critical · errMasterPasswordMigrationPending
master password migration is pending: Master password change
Error message
master password migration is pending: Master password change partially failed. Please restart SiYuan to complete recovery. Detail: %s: save conf failed: %s
What it means
Returned by ChangeMasterPassword Phase 3 (crypto.go:1805-1812) when the main conf.json write for a notebook fails after its BoxCrypt fields were updated in memory to the new KEK's WrappedDEK/Spec/WrapNonce/Metadata. It wraps errMasterPasswordMigrationPending (localized 320) with the box ID and the save error; the migration manifest ensures a restart can finish the job.
Source
Thrown at kernel/model/crypto.go:1810
boxConf.BoxCrypt = &conf.BoxEncryption{
WrappedDEK: entry.NewWrappedDEK,
WrapNonce: entry.NewWrapNonce,
Spec: entry.NewSpec,
Metadata: entry.Metadata,
CreatedAt: time.Now().UnixMilli(),
}
if saveErr := box.SaveConf(boxConf); saveErr != nil {
return fmt.Errorf("%w: %s", errMasterPasswordMigrationPending,
fmt.Sprintf(Conf.Language(320), entry.BoxID+": rebuild encrypted conf from migration entry failed: "+saveErr.Error()))
}
}
}
boxConf.BoxCrypt.WrappedDEK = entry.NewWrappedDEK
boxConf.BoxCrypt.Spec = entry.NewSpec
boxConf.BoxCrypt.WrapNonce = entry.NewWrapNonce
boxConf.BoxCrypt.Metadata = append([]byte(nil), entry.Metadata...)
if err = box.SaveConf(boxConf); err != nil {
return fmt.Errorf("%w: %s", errMasterPasswordMigrationPending,
fmt.Sprintf(Conf.Language(320), entry.BoxID+": save conf failed: "+err.Error()))
}
if err = writeNotebookCryptBackup(entry.BoxID, boxConf.BoxCrypt); err != nil {
return fmt.Errorf("%w: %s", errMasterPasswordMigrationPending,
fmt.Sprintf(Conf.Language(320), entry.BoxID+": update notebook crypt backup failed: "+err.Error()))
}
}
// Phase 4: 先持久化全局备份,再清除 manifest,确保崩溃后可恢复
if err = saveNotebookCryptoBackup(newKEK); err != nil {
return fmt.Errorf("%w: %s", errMasterPasswordMigrationPending,
fmt.Sprintf(Conf.Language(320), "save notebook crypto backup failed: "+err.Error()))
}
removeMasterPasswordMigration()
IncSync()
return nil
}
View on GitHub (pinned to afa823b6b4)
Solutions
- Resolve the disk/permission/lock condition reported in the detail string
- Restart SiYuan to let migration recovery complete, then verify with the new master password
- Retry the password change only after recovery reports clean; ensure ample free space and paused sync/AV during the operation
Defensive patterns
Strategy: type-guard
Type guard
func isMigrationPendingErr(err error) bool {
return errors.Is(err, errMasterPasswordMigrationPending) // in-package; outside use string prefix check
} Try / catch
if err := model.ChangeMasterPassword(oldPw, newPw); isMigrationPendingErr(err) {
// conf save failed mid-loop: do not retry the change in-session; restart and verify
instructUserRestart()
return
} Prevention
- Free disk space exceeding the total size of all notebook conf files before the change
- Pause sync/AV file locks on the workspace during password rotation
- Treat any single SaveConf failure as 'restart and recover', never as 'retry immediately'
When it happens
Trigger: box.SaveConf errors while persisting the re-wrapped key material: disk full, read-only file, permission denied, or an external process holding conf.json during the rewrite loop.
Common situations: Password change over many notebooks running out of space midway; AV/sync locking conf.json files one by one; workspace on flaky storage.
Related errors
- master password migration is pending: Master password change
- master password migration is pending: Master password change
- master password migration is pending: %v
- master password migration is pending: Master password change
- master password migration is pending: Master password change
AI-assisted analysis of siyuan-note/siyuan@afa823b6b4 (2026-08-18).
Data as JSON: /api/errors/a75cd92f44c8bb5b.
Report an issue: GitHub.