siyuan-note/siyuan · warning

Related operations are being processed, please try again…

Error message

Related operations are being processed, please try again later

What it means

Returned by unlockBoxHeld (kernel/model/crypto.go:1371-1372, localized message 239 'Related operations are being processed, please try again later') when the per-notebook lock registry boxLock already holds an entry for the boxID, meaning another lock/unlock/unmount lifecycle operation for the same notebook is in flight. It is a transient busy signal guarding the DEK/db state machine, not a crypto failure; the request made no state changes.

Solutions

  1. Retry the same unlock call after a short delay (500ms-2s) - the in-flight operation will have finished
  2. Debounce/disable the unlock button in the client until the first call returns
  3. Serialize all lock/unlock/unmount calls per notebook in your client code instead of firing them in parallel

Example fix

// before: fire-and-forget concurrent unlock
go api.unlockNotebook(boxID, password)
go api.unlockNotebook(boxID, password) // may hit busy error 239

// after: serialize + bounded retry on the busy message
for attempt := 0; attempt < 3; attempt++ {
    err := model.UnlockBox(boxID, password, boxCrypt)
    if err == nil {
        break
    }
    if strings.Contains(err.Error(), Conf.Language(239)) {
        time.Sleep(time.Duration(500*(attempt+1)) * time.Millisecond)
        continue
    }
    return err // real failure, do not retry
}
Defensive patterns

Strategy: retry

Try / catch

var err error
for attempt := 0; attempt < 3; attempt++ {
    err = model.UnlockBox(boxID, password, boxCrypt)
    if err == nil || !strings.Contains(err.Error(), Conf.Language(239)) {
        break // not the busy error: stop retrying
    }
    time.Sleep(time.Duration(400*(attempt+1)) * time.Millisecond)
}

Prevention

When it happens

Trigger: Two concurrent UnlockBox/LockBox/UnmountBox/UnlockAndMountBox calls for the same notebook: user double-clicks the unlock button, an API client retries while the first request is still inside acquireBoxWriteLock, or the auto-lock timer fires while the user is unlocking. Each call runs a ~1s Argon2id derivation, so the busy window is wide enough to hit.

Common situations: Frontend not debouncing the unlock dialog submit; batch scripts hammering /api/notebox unlock endpoints; auto-lock (AutoLockMinutes) racing a manual re-unlock after a failed attempt.

Related errors


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

Appendix: source

Thrown at kernel/model/crypto.go:1372

	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 afa823b6b4)