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
- Retry the same unlock call after a short delay (500ms-2s) - the in-flight operation will have finished
- Debounce/disable the unlock button in the client until the first call returns
- 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
- Debounce the unlock/lock UI actions so a second submit cannot race the first
- Serialize all notebook lifecycle calls (unlock/lock/unmount) per boxID in client code
- Remember each unlock runs ~1s of Argon2id - design timeouts and retries around that window
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
- 239
- attribute view [ ] changed while loading search info
- cannot replay block swap across encrypted notebook…
- cannot swap blocks across encrypted notebook boundaries
- CLI does not support encrypted notebook
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)