siyuan-note/siyuan · critical

master password migration is pending

Error message

master password migration is pending

What it means

errMasterPasswordMigrationPending is a sentinel marking an unfinished master-password change: the global verifier was already switched to the new KEK, but some per-notebook WrappedDEK material or the global backup has not been fully persisted (crash mid-migration, or a per-box conf/backup save failing in ChangeMasterPassword phases 3-4). It is returned bare when recovery still cannot verify all boxes against the new KEK, and wrapped with box-level detail (%w) when re-writing a box conf, per-notebook backup, or the global backup fails during recovery/continuation.

Source

Thrown at kernel/model/crypto.go:166

	})
	if errors.Is(err, errEncryptedNotebookPayloadFound) {
		return true, nil
	}
	return false, err
}

const encryptedAssetMetadataMaxSize = 1024 * 1024
const encryptedAssetChunkSize = 1024 * 1024
const encryptedAssetChunkMaxCiphertextSize = encryptedAssetChunkSize + 1024

type encryptedAssetMetadata struct {
	OriginalName string `json:"originalName"`
	Size         int64  `json:"size"`
	Chunks       uint64 `json:"chunks"`
}

// errMasterPasswordMigrationPending 表示改密已切换全局 verifier,但部分笔记本配置尚待恢复。
var errMasterPasswordMigrationPending = errors.New("master password migration is pending")

// notebookCryptoMu 串行化加密笔记本的控制面操作(Enable/Disable/Create/ChangeMasterPassword/Import/restore 等),
// 避免 ChangeMasterPassword 枚举与 CreateEncryptedBox 并发导致新笔记本用旧 KEK 但 verifier 已切换的不可恢复状态。
var notebookCryptoMu sync.Mutex

var masterPasswordMigrationMu sync.Mutex

// boxLifecycleLocks 为每个 box 提供一个 RWMutex,协调锁定操作与在途解密请求。
// 在途解密请求持读锁,LockBox 持写锁,确保锁定后不会有新的解密输出。
var boxLifecycleLocks = sync.Map{} // map[string]*sync.RWMutex

func acquireBoxReadLock(boxID string) {
	muI, _ := boxLifecycleLocks.LoadOrStore(boxID, &sync.RWMutex{})
	muI.(*sync.RWMutex).RLock()
}

func releaseBoxReadLock(boxID string) {
	if muI, ok := boxLifecycleLocks.Load(boxID); ok {

View on GitHub (pinned to afa823b6b4)

Solutions

  1. Unlock/verify again with the NEW master password - the recovery path (recoverMasterPasswordMigration) re-runs and completes the pending writes
  2. Make sure the workspace disk is writable and no second kernel instance or sync tool is touching conf files, then retry
  3. Do not attempt the old password - the global verifier has already switched, which is exactly what this sentinel reports
  4. Once unlocked, export the key backup (ExportNotebookCryptoBackup) so a future interruption is recoverable offline
Defensive patterns

Strategy: try-catch

Try / catch

err := model.CheckMasterPassword(password) // or the unlock flow
if err != nil {
    if strings.Contains(err.Error(), "master password migration is pending") {
        // interrupted change-password: retry with the NEW password until recovery completes; never fall back to the old one
    }
}

Prevention

When it happens

Trigger: kernel killed or crashed between writeMasterPasswordMigration and removeMasterPasswordMigration; disk full or filelock contention while ChangeMasterPassword rewrites box confs; then the next unlock with the new password fails verifyKEKAgainstExistingBoxes or saveNotebookCryptoBackup and returns this sentinel (kernel/model/crypto.go:1279,1283,1783-1821).

Common situations: Force-quitting the app during a master password change; workspace on a flaky/synced drive where conf writes fail; two operations racing (the code serializes via notebookCryptoMu/masterPasswordMigrationMu, but external processes bypass that).

Related errors


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