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: update notebook crypt backup failed: %s

What it means

Returned by ChangeMasterPassword Phase 3 (crypto.go:1813-1816) when writeNotebookCryptBackup fails after the notebook's conf.json was already updated to the new key material. The per-notebook backup is the redundancy that lets decryptBoxCrypt fall back and repair conf, so failing to refresh it aborts with errMasterPasswordMigrationPending (localized 320); the manifest survives for restart recovery.

Source

Thrown at kernel/model/crypto.go:1814

					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
}

// IsEncryptedBox 判断给定 boxID 是否为加密笔记本。
// 配置缺失或损坏时依次检查运行时身份、独立备份和密文标识,任何检查错误都按加密笔记本处理。
func IsEncryptedBox(boxID string) bool {
	if !ast.IsNodeIDPattern(boxID) {

View on GitHub (pinned to afa823b6b4)

Solutions

  1. Restore writability of the notebook crypt backup location named in the logs
  2. Restart SiYuan - recovery re-runs the manifest and rewrites both conf and backups; verify with the new password afterwards
  3. Keep enough free space and pause third-party file locks before rotating the master password
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) {
    instructUserRestart() // backup refresh failed; manifest recovery rewrites conf+backup pairs
    return
}

Prevention

When it happens

Trigger: The backup file under the notebook crypto backup path cannot be written: disk full, permission denied, or the backup file locked by another process during the change loop.

Common situations: Backup directory made read-only or excluded incorrectly; disk exhaustion partway through the per-notebook backup writes; file-level locks from backup software.

Related errors


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