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: save conf failed: %s

What it means

Returned by ChangeMasterPassword Phase 3 (crypto.go:1805-1812) when the main conf.json write for a notebook fails after its BoxCrypt fields were updated in memory to the new KEK's WrappedDEK/Spec/WrapNonce/Metadata. It wraps errMasterPasswordMigrationPending (localized 320) with the box ID and the save error; the migration manifest ensures a restart can finish the job.

Source

Thrown at kernel/model/crypto.go:1810

				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()))
				}
			}
		}
		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
}

View on GitHub (pinned to afa823b6b4)

Solutions

  1. Resolve the disk/permission/lock condition reported in the detail string
  2. Restart SiYuan to let migration recovery complete, then verify with the new master password
  3. Retry the password change only after recovery reports clean; ensure ample free space and paused sync/AV during the operation
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) {
    // conf save failed mid-loop: do not retry the change in-session; restart and verify
    instructUserRestart()
    return
}

Prevention

When it happens

Trigger: box.SaveConf errors while persisting the re-wrapped key material: disk full, read-only file, permission denied, or an external process holding conf.json during the rewrite loop.

Common situations: Password change over many notebooks running out of space midway; AV/sync locking conf.json files one by one; workspace on flaky storage.

Related errors


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