siyuan-note/siyuan · error

: (errMasterPasswordMigrationPending; Conf.Language(320)…

Error message

%w: %s (errMasterPasswordMigrationPending; Conf.Language(320) formatted)

What it means

During master-password migration Phase 3 (recovering a partially applied migration), if a box's conf.json cannot be saved after rebuilding its BoxCrypt from the per-notebook backup, the kernel returns errMasterPasswordMigrationPending with a localized detail (Conf.Language(320)) so the migration is retried on next startup. The pending-marker error signals 'not finished, recovery will run again', and the embedded string carries the box ID and underlying save error.

Solutions

  1. Restart SiYuan - the pending migration will be retried automatically on boot (that is what errMasterPasswordMigrationPending is for)
  2. Free disk space and verify the workspace data directory is writable
  3. Check for processes locking the notebook conf.json (sync clients, antivirus) and exclude the workspace from them
  4. If SaveConf keeps failing, fix the specific cause from the embedded saveErr detail in the error text

Example fix

// before: conf.json on a full disk cannot be written
// -> Master password change partially failed... box=...: rebuild encrypted conf from backup failed: write error
// after: free space on the workspace volume, restart SiYuan so the pending migration recovery reruns to completion
Defensive patterns

Strategy: retry

Validate before calling

// Before migration, verify conf.json of each encrypted notebook is writable
confPath := filepath.Join(util.WorkspaceDir, "data", boxID, ".siyuan", "conf.json")
if f, err := os.OpenFile(confPath, os.O_WRONLY, 0o600); err != nil {
    return err
} else { f.Close() }

Try / catch

if err := model.ChangeMasterPassword(old, new); err != nil {
    if errors.Is(err, model.ErrMasterPasswordMigrationPending) {
        // migration partially applied; restart SiYuan - startup recovery retries automatically
    }
}

Prevention

When it happens

Trigger: recoverMasterPasswordMigration processes a migration entry; readNotebookCryptBackup succeeds, box.SaveConf(boxConf) fails while writing the rebuilt encrypted conf (disk full, permission denied, file lock held, JSON write error).

Common situations: Disk quota exhausted mid-migration, antivirus/file-sync tools locking conf.json, read-only workspace after a crash-restore, or interrupted previous run leaving the migration manifest in place.

Understand the failure class

Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/503b1f72440854cb. Report an issue: GitHub.

Appendix: 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 9f775e8a12)