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: save notebook crypto backup failed: %s

What it means

Returned by ChangeMasterPassword Phase 4 (crypto.go:1819-1823) when the final authenticated global notebook-crypto backup (saveNotebookCryptoBackup) fails after all per-notebook confs and backups were already migrated to the new KEK. Because the manifest is only removed after this write succeeds, the change is deliberately left 'pending'; restarting and verifying the new password re-attempts exactly this step.

Source

Thrown at kernel/model/crypto.go:1821

			}
		}
		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) {
		return false
	}
	if isRuntimeEncryptedBox(boxID) {
		return true
	}
	if isRuntimeNormalBox(boxID) {
		return false

View on GitHub (pinned to afa823b6b4)

Solutions

  1. Fix the write condition on the global backup path (space, permissions, locks)
  2. Restart SiYuan and submit the new master password - verification detects the pending manifest, re-checks the boxes and retries saveNotebookCryptoBackup
  3. Only after recovery completes (no more 'migration is pending' on verification) treat the password change as done
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) {
    // notebooks migrated but global backup failed: manifest retained on purpose,
    // restart + verify with the new password runs exactly this last step again
    instructUserRestart()
    return
}

Prevention

When it happens

Trigger: Global backup file write failure at the very end of the change: disk full, permission denied, or a lock on the global notebook crypto backup path - after all notebook-level writes already succeeded.

Common situations: Disk hitting its limit at the last write of a long migration; AV quarantining/locking the global backup file; workspace directory permission changed mid-operation.

Related errors


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