siyuan-note/siyuan · warning

Related operations are being processed, please try again lat

Error message

Related operations are being processed, please try again later

What it means

Thrown by unlockBoxHeld (crypto.go:1371, i18n code 239) when boxLock already has an entry for this boxID, meaning another mount/unmount/history operation is currently in progress on that notebook. This is a transient concurrency guard: the requested unlock cannot proceed in parallel with the in-flight operation and the caller should retry shortly. Unlike the transition lock (holdEncryptedBoxTransition, which blocks waiting), this busy flag fails fast.

Source

Thrown at kernel/model/crypto.go:1371

	notebookCryptoMu.Lock()
	defer notebookCryptoMu.Unlock()
	releaseTransition := holdEncryptedBoxTransition(boxID)
	defer releaseTransition()

	wasUnlocked := IsBoxUnlocked(boxID)
	if err = unlockBoxHeld(boxID, password, boxEnc); err != nil {
		return false, err
	}
	alreadyMount, err = mountBox(boxID)
	if err != nil && !wasUnlocked {
		lockBoxWithPreparationHeld(boxID, nil)
	}
	return alreadyMount, err
}

func unlockBoxHeld(boxID string, password string, boxEnc *conf.BoxEncryption) (err error) {
	if _, busy := boxLock.Load(boxID); busy {
		return errors.New(Conf.language(239))
	}
	if boxEnc == nil || len(boxEnc.WrappedDEK) == 0 {
		setEncryptedBoxState(boxID, EncryptedBoxStateError)
		return errors.New("no encrypted key material for box")
	}
	if IsBoxUnlocked(boxID) {
		if GetEncryptedBoxState(boxID) == EncryptedBoxStateError {
			return errors.New(Conf.Language(316))
		}
		setEncryptedBoxState(boxID, EncryptedBoxStateUnlocked)
		return nil
	}

	setEncryptedBoxState(boxID, EncryptedBoxStateUnlocking)
	// 获取 box 写锁,与 LockBox/unmount0 串行化,防止并发锁/解锁导致 db/DEK 状态不一致
	acquireBoxWriteLock(boxID)
	finalState := EncryptedBoxStateLocked
	defer func() {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Retry the unlock after the in-flight operation completes (this is the intended handling; the error is transient).
  2. Debounce/disable the unlock button in the UI until the current operation returns to prevent repeated triggers.
  3. Avoid calling UnlockBox/UnlockAndMountBox concurrently for the same notebook from multiple paths.
Defensive patterns

Strategy: retry

Try / catch

// Transient busy state: back off and retry a few times.
const maxRetries = 5
for i := 0; i < maxRetries; i++ {
    err := model.UnlockBox(boxID, password, boxCrypt)
    if err == nil {
        break
    }
    if err.Error() == model.Conf.Language(239) {
        time.Sleep(backoff(i)) // exponential backoff
        continue
    }
    return err
}

Prevention

When it happens

Trigger: unlockBoxHeld is called (via UnlockBox or UnlockAndMountBox) while boxLock.LoadOrStore for the same boxID is held by mountBox, unmount0, or a history operation (mount.go / history.go). E.g. user double-clicks unlock while a mount is mid-flight, or a sync-triggered remount overlaps a manual unlock.

Common situations: Rapid repeated unlock clicks. Concurrent sync/export touching the same notebook. UI retry storm during slow Argon2id derivation on the same box.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/1c642ab9fc30f0f7. Report an issue: GitHub.