{"record":{"id":"61fb44bf48f2cf22","repo":"iflytek/astron-agent","slug":"23902-conversation-does-not-exist-or-is-not-locked","errorCode":"23902","errorMessage":"Conversation does not exist or is not locked","messagePattern":"Conversation does not exist or is not locked","errorType":"error_code","errorClass":"CustomException","httpStatus":null,"severity":"warning","filePath":"core/workflow/cache/event_registry.py","lineNumber":131,"sourceCode":"        :raise Exception: Raises exception if saving event fails\n        \"\"\"\n        try:\n            cls.save_event(event)\n        except Exception as e:\n            raise e\n\n    @classmethod\n    def lock_event(cls, event_id: str, sid: str, timeout: int = 180) -> None:\n        get_cache_service().set_ex(\n            key=f\"event_lock:{event_id}\",\n            value=f\"locked_by_{sid}\",\n            expire_time=timeout,\n        )\n\n    @classmethod\n    def unlock_event(cls, event_id: str) -> None:\n        if not cls.check_event_lock(event_id=event_id):\n            raise CustomException(err_code=CodeEnum.EVENT_REGISTRY_NOT_LOCK_ERROR)\n        get_cache_service().delete(key=f\"event_lock:{event_id}\")\n\n    @classmethod\n    def check_event_lock(cls, event_id: str) -> bool:\n        data = get_cache_service().get(key=f\"event_lock:{event_id}\")\n        if not data:\n            return False\n        return True\n\n    @classmethod\n    def get_event(cls, event_id: str) -> Event:\n        \"\"\"\n        Get event information by event ID.\n\n        :param cls: Class itself\n        :param event_id: Event ID string\n        :return: Decoded event object if found, raises exception otherwise\n        \"\"\"","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/iflytek/astron-agent/blob/5e758547a83371a5a4b29dadf4ac03e8dd527635/core/workflow/cache/event_registry.py#L113-L149","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before\nfinally:\n    EventRegistry().unlock_event(event_id=event_id)  # raises if never locked\n\n# after\nlocked = False\ntry:\n    EventRegistry().lock_event(event_id=event_id, sid=sid)\n    locked = True\n    ...\nfinally:\n    if locked and EventRegistry().check_event_lock(event_id=event_id):\n        EventRegistry().unlock_event(event_id=event_id)","handlingStrategy":"try-catch","validationCode":"if EventRegistry().check_event_lock(event_id=event_id):\n    EventRegistry().unlock_event(event_id=event_id)","typeGuard":"def is_locked(event_id: str) -> bool:\n    return bool(EventRegistry().check_event_lock(event_id=event_id))","tryCatchPattern":"try:\n    EventRegistry().unlock_event(event_id=event_id)\nexcept CustomException as e:\n    if e.code == CodeEnum.EVENT_REGISTRY_NOT_LOCK_ERROR.code:\n        pass  # already unlocked — treat as success in cleanup\n    else:\n        raise","preventionTips":["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"],"tags":["cache","locking","cleanup","event-registry"],"backgroundTag":"invalid-state-transition","analyzedSha":"5e758547a83371a5a4b29dadf4ac03e8dd527635","analyzedAt":"2026-09-12T08:03:51.356Z","contentChangedAt":"2026-09-12T08:03:51.356Z","schemaVersion":2},"datasetVersion":"2026-09-19T12:17:13.211Z"}