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
- Retry the unlock after the in-flight operation completes (this is the intended handling; the error is transient).
- Debounce/disable the unlock button in the UI until the current operation returns to prevent repeated triggers.
- 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
- Debounce the unlock button in the UI so rapid clicks do not stack.
- Avoid concurrent unlock and sync/export on the same notebook.
- Treat code 239 as transient and retry with backoff, not as a hard failure.
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
- Related operations are being processed, please try again lat
- encrypted notebook is locked, please unlock it first
- refresh OAuth credentials: %w
- 315
- enable encrypted notebook failed: failed to persist key back
AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12).
Data as JSON: /api/errors/1c642ab9fc30f0f7.
Report an issue: GitHub.