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: rebuild encrypted conf from backup failed: %s

What it means

Returned by ChangeMasterPassword Phase 3 (crypto.go:1777-1785) when a notebook's conf.json was missing/corrupt, the per-notebook backup was successfully read as the rebuild source, but writing the rebuilt conf back to disk (box.SaveConf) failed. It wraps errMasterPasswordMigrationPending with localized message 320, telling the user to restart; the migration manifest written in Phase 1 remains the authoritative recovery record.

Source

Thrown at kernel/model/crypto.go:1783

	Conf.NotebookCrypto.KDFParams = params
	Conf.m.Unlock()

	// Conf.Save 内部会加 Conf.m,不能在持锁状态下调用(RWMutex 不可重入)
	Conf.Save()

	// Phase 3: 写入各 box conf + backup
	for _, entry := range entries {
		box := &Box{ID: entry.BoxID}
		boxConf := box.GetConf()
		if !boxConf.Encrypted || boxConf.BoxCrypt == nil {
			// conf 缺失/损坏:尝试从 per-notebook backup 重建
			backup, bErr := readNotebookCryptBackup(entry.BoxID)
			if bErr == nil && backup != nil && len(backup.WrappedDEK) > 0 {
				boxConf = box.GetConf()
				boxConf.Encrypted = true
				boxConf.BoxCrypt = backup
				if saveErr := box.SaveConf(boxConf); saveErr != nil {
					return fmt.Errorf("%w: %s", errMasterPasswordMigrationPending,
						fmt.Sprintf(Conf.Language(320), entry.BoxID+": rebuild encrypted conf from backup failed: "+saveErr.Error()))
				}
			} else {
				// conf 与 backup 均不可用:manifest 是该 box 加密密钥的权威来源,直接从 entry 重建 BoxCrypt,
				// 避免改密因瞬时 conf 损坏而中断(详见 recoverMasterPasswordMigration 中的对称处理)。
				logging.LogWarnf("rebuild encrypted box [%s] from migration entry (conf and backup both unavailable)", entry.BoxID)
				boxConf = box.GetConf()
				boxConf.Encrypted = true
				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()))

View on GitHub (pinned to afa823b6b4)

Solutions

  1. Free disk space / clear the file lock / fix permissions on the notebook's .siyuan/conf.json
  2. Restart SiYuan - recoverMasterPasswordMigration replays the manifest and completes the switch; then verify with the NEW password
  3. Confirm via kernel logs that recovery finished before unlocking notebooks or changing the password again
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) {
    // change half-done BY DESIGN: manifest is on disk, restart + new password completes it
    showUser(Conf.Language(320)) // 'restart SiYuan to complete recovery'
    return
}

Prevention

When it happens

Trigger: Disk full, permission denied, or a file lock on <data>/<boxID>/.siyuan/conf.json exactly while the change-password loop rewrites notebook configs; antivirus/indexer briefly locking the file on Windows.

Common situations: Insufficient disk space during a many-notebook migration; workspace guarded by sync/AV software; conf.json made read-only by backup tools.

Related errors


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