siyuan-note/siyuan · error · errMasterPasswordMigrationPending

%w: %s (errMasterPasswordMigrationPending, Conf.Language(320

Error message

%w: %s (errMasterPasswordMigrationPending, Conf.Language(320))

What it means

In ChangeMasterPassword Phase 3, when a box's conf is missing/corrupted the code rebuilds it from the per-notebook crypt backup; if saving that rebuilt conf fails, the function returns errMasterPasswordMigrationPending wrapped with localized message 320. The global verifier and migration manifest have already been committed, so the migration is intentionally left 'pending' and will be finished by the recovery routine (recoverMasterPasswordMigration) on next startup. The error signals the user must restart so recovery can complete the per-box phase.

Source

Thrown at kernel/model/crypto.go:1810

	Conf.NotebookCrypto.HistoryKEKs = newHistoryKEKs
	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 8641553a1f)

Solutions

  1. Restart SiYuan: the pending migration manifest triggers automatic recovery of the per-box confs on boot.
  2. Fix the underlying write failure (free disk space, close the app/lock holder, fix permissions on <data>/<boxID>/.siyuan/conf.json) and let recovery finish.
  3. If recovery keeps failing, verify the workspace data dir is writable and not open in a second instance, then restart again.

Example fix

// before: retrying password change immediately in the same session
ChangeMasterPassword(old, new) // fails again, still pending
// after: restart the kernel so recoverMasterPasswordMigration completes,
// then confirm state before any further crypto operations
Defensive patterns

Strategy: retry

Validate before calling

// Check prerequisites before starting the migration
if filelock.IsExist(confPath) == false || !isDirWritable(util.DataDir) {
    return errors.New("data dir must be writable before changing master password")
}
if len(model.CachedEncryptedDEKs()) > 0 {
    return errors.New("lock all encrypted notebooks before changing master password")
}

Type guard

func isPendingMigrationErr(err error) bool {
    return errors.Is(err, model.ErrMasterPasswordMigrationPending)
}

Try / catch

if err := model.ChangeMasterPassword(old, new); err != nil {
    if errors.Is(err, model.ErrMasterPasswordMigrationPending) {
        // do NOT retry with the old password; prompt the user to restart
        // the kernel — recovery will complete the pending migration on boot
        return err
    }
    return err
}

Prevention

When it happens

Trigger: Calling the change-master-password API when a box conf is missing/BoxCrypt==nil, a valid per-notebook backup exists, but box.SaveConf fails (e.g. conf.json locked, unwritable data dir, disk full, filelock timeout).

Common situations: Data directory on a read-only/full disk or synced drive holding file locks; antivirus or another SiYuan instance locking .siyuan/conf.json; permission problems after restoring data as a different user.

Related errors


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