iflytek/astron-agent · warning · CustomException
23902
23902
Error message
Conversation does not exist or is not locked
What it means
EventRegistry.unlock_event (core/workflow/cache/event_registry.py:131) raises EVENT_REGISTRY_NOT_LOCK_ERROR when check_event_lock(event_id) is false — i.e. no event_lock:{event_id} key exists in the cache — so there is nothing to unlock. Unlocking is only valid for an event that currently holds a lock (code 23902).
Solutions
- Only call unlock_event when you know your code path acquired the lock (track ownership with a boolean).
- Check EventRegistry().check_event_lock(event_id) before unlocking, mirroring the guard inside the method.
- Treat 'not locked' as success in cleanup paths — the goal (no lock) is already achieved.
- If locks are persistently missing/expiring early, review the cache TTL configuration and clock/TTL behavior of the cache service.
Example fix
# before
finally:
EventRegistry().unlock_event(event_id=event_id) # raises if never locked
# after
locked = False
try:
EventRegistry().lock_event(event_id=event_id, sid=sid)
locked = True
...
finally:
if locked and EventRegistry().check_event_lock(event_id=event_id):
EventRegistry().unlock_event(event_id=event_id) Defensive patterns
Strategy: try-catch
Validate before calling
if EventRegistry().check_event_lock(event_id=event_id):
EventRegistry().unlock_event(event_id=event_id) Type guard
def is_locked(event_id: str) -> bool:
return bool(EventRegistry().check_event_lock(event_id=event_id)) Try / catch
try:
EventRegistry().unlock_event(event_id=event_id)
except CustomException as e:
if e.code == CodeEnum.EVENT_REGISTRY_NOT_LOCK_ERROR.code:
pass # already unlocked — treat as success in cleanup
else:
raise Prevention
- Unlock only in the same scope that acquired the lock; track ownership with a flag
- Remember locks expire via TTL — a late finally-block may find nothing to unlock
- Never unlock an event your request did not lock
When it happens
Trigger: Calling unlock_event for an event that was never locked, whose lock already expired (TTL) or was deleted by a prior unlock; race where two cleanup paths unlock concurrently and the second finds no key.
Common situations: Error-handling code unconditionally calls unlock_event even when the resume path never reached lock_event; lock TTL elapsed during a long-running resume and a finally-block tries to unlock; duplicate unlock calls from nested exception handlers.
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
- 23900
- 23900
- 23901
- Failed to parse user ID from pre-binding information: appid=
- lock tenant bootstrap app failed
AI-assisted analysis of iflytek/astron-agent@5e758547a8 (2026-09-12).
Data as JSON: /api/errors/61fb44bf48f2cf22.
Report an issue: GitHub.
Appendix: source
Thrown at core/workflow/cache/event_registry.py:131
:raise Exception: Raises exception if saving event fails
"""
try:
cls.save_event(event)
except Exception as e:
raise e
@classmethod
def lock_event(cls, event_id: str, sid: str, timeout: int = 180) -> None:
get_cache_service().set_ex(
key=f"event_lock:{event_id}",
value=f"locked_by_{sid}",
expire_time=timeout,
)
@classmethod
def unlock_event(cls, event_id: str) -> None:
if not cls.check_event_lock(event_id=event_id):
raise CustomException(err_code=CodeEnum.EVENT_REGISTRY_NOT_LOCK_ERROR)
get_cache_service().delete(key=f"event_lock:{event_id}")
@classmethod
def check_event_lock(cls, event_id: str) -> bool:
data = get_cache_service().get(key=f"event_lock:{event_id}")
if not data:
return False
return True
@classmethod
def get_event(cls, event_id: str) -> Event:
"""
Get event information by event ID.
:param cls: Class itself
:param event_id: Event ID string
:return: Decoded event object if found, raises exception otherwise
"""View on GitHub (pinned to 5e758547a8)