siyuan-note/siyuan · error
encrypted notebook operation scope is closed
Error message
encrypted notebook operation scope is closed
What it means
`ErrEncryptedBoxOperationScopeClosed` is returned when code tries to acquire leases from a response-scoped operation scope that has already been closed (its close function was called at the end of the outer request). Scopes exist to tie all box leases to one request lifecycle, so any acquisition after close is a programming error.
Source
Thrown at kernel/model/crypto_lifecycle.go:205
lifecycle.condition.Wait()
}
lifecycle.lock.Unlock()
}
type encryptedBoxOperationScope struct {
lock sync.Mutex
boxIDs []string
boxIDSet map[string]struct{}
closed bool
}
type encryptedBoxOperationScopeKey struct{}
var (
// ErrEncryptedBoxNotUnlocked 表示加密笔记本当前未解锁。
ErrEncryptedBoxNotUnlocked = errors.New("encrypted notebook is not unlocked")
// ErrEncryptedBoxOperationScopeClosed 表示响应级操作作用域已经关闭。
ErrEncryptedBoxOperationScopeClosed = errors.New("encrypted notebook operation scope is closed")
)
// WithEncryptedBoxOperationScope 创建覆盖整个外层响应过程的租约作用域。
func WithEncryptedBoxOperationScope(ctx context.Context) (context.Context, func()) {
scope := &encryptedBoxOperationScope{boxIDSet: map[string]struct{}{}}
scopedContext := context.WithValue(ctx, encryptedBoxOperationScopeKey{}, scope)
return scopedContext, scope.release
}
// AcquireEncryptedBoxOperations 按固定顺序取得多个笔记本的响应级租约。
func AcquireEncryptedBoxOperations(ctx context.Context, boxIDs []string) (release func(), err error) {
unique := map[string]struct{}{}
for _, boxID := range boxIDs {
if boxID != "" && IsEncryptedBox(boxID) {
unique[boxID] = struct{}{}
}
}
sortedBoxIDs := make([]string, 0, len(unique))View on GitHub (pinned to 8641553a1f)
Solutions
- Move lease acquisition into the synchronous part of the request handler, before the deferred scope close runs.
- If background work is needed, acquire leases before spawning the goroutine or use a context that is not tied to the closed scope.
- Structure the handler so the scope close func runs only after all lease-holding work completes.
- Check for double-invocation of the scope's close function in wrapper middleware.
Example fix
// before
scopeCtx, closeScope := model.WithEncryptedBoxOperationScope(ctx)
go func() { model.AcquireEncryptedBoxOperations(scopeCtx, ids) }() // scope may close first
closeScope()
// after
scopeCtx, closeScope := model.WithEncryptedBoxOperationScope(ctx)
_, err := model.AcquireEncryptedBoxOperations(scopeCtx, ids) // acquire synchronously
if err != nil { return err }
closeScope() Defensive patterns
Strategy: try-catch
Try / catch
_, err := model.AcquireEncryptedBoxOperations(scopeCtx, boxIDs)
if errors.Is(err, model.ErrEncryptedBoxOperationScopeClosed) {
// scope already closed: move this work into the request's synchronous path
return fmt.Errorf("lease requested after request scope closed: %w", err)
} Prevention
- Acquire all box leases synchronously before returning from or deferring close of the scope.
- Never pass the scoped context to goroutines that may outlive the handler.
- Keep the scope close deferred only after all lease-holding work completes.
When it happens
Trigger: Calling AcquireEncryptedBoxOperations with a context whose scope was closed by the deferred close function from WithEncryptedBoxOperationScope; also asserted by the test TestAcquireEncryptedBoxOperationsReportsClosedScope.
Common situations: Launching a goroutine that outlives the HTTP request and tries to take leases after the request handler returned; forgetting that the scope close is deferred at the start of the response flow.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- encrypted notebook is not unlocked
- master password migration is pending
- Please unlock the encrypted notebook first
- Please unlock the encrypted notebook first
- encrypted notebook is locked, please unlock it first
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/f7ad36a6ccd69996.
Report an issue: GitHub.