{"record":{"id":"6f22681a2b254b23","repo":"python/cpython","slug":"object-type-mismatch-in-limited-api-annotation-for","errorCode":null,"errorMessage":"Object type mismatch in limited API annotation for {name}: {ROLE_TO_OBJECT_TYPE[record.role]!r} != {objtype!r}","messagePattern":"Object type mismatch in limited API annotation for (.+?): (.+?) != (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Doc/tools/extensions/c_annotations.py","lineNumber":185,"sourceCode":"        if not par[0].get(\"ids\", None):\n            continue\n        name = par[0][\"ids\"][0].removeprefix(\"c.\")\n        objtype = par[\"objtype\"]\n\n        # Thread safety annotation — inserted first so it appears last (bottom-most)\n        # among all annotations.\n        if entry := threadsafety_data.get(name):\n            annotation = _threadsafety_annotation(entry.level)\n            node.insert(0, annotation)\n\n        # Stable ABI annotation.\n        if record := stable_abi_data.get(name):\n            if ROLE_TO_OBJECT_TYPE[record.role] != objtype:\n                msg = (\n                    f\"Object type mismatch in limited API annotation for {name}: \"\n                    f\"{ROLE_TO_OBJECT_TYPE[record.role]!r} != {objtype!r}\"\n                )\n                raise ValueError(msg)\n            annotation = _stable_abi_annotation(record)\n            node.insert(0, annotation)\n\n        # Unstable API annotation.\n        if name.startswith(\"PyUnstable\"):\n            annotation = _unstable_api_annotation()\n            node.insert(0, annotation)\n\n        # Return value annotation\n        if objtype != \"function\":\n            continue\n        if name not in refcount_data:\n            continue\n        entry = refcount_data[name]\n        if not entry.result_type.endswith(\"Object*\"):\n            continue\n        annotation = _return_value_annotation(entry.result_refs)\n        node.insert(0, annotation)","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/python/cpython/blob/bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6/Doc/tools/extensions/c_annotations.py#L167-L203","documentation":"Runner._lazy_init() raises RuntimeError when the Runner has already been closed (used as a context manager and exited, or close() called explicitly). Any later attempt to run() or get_loop() re-initializes lazily and hits this state check. A closed Runner is terminal and must be replaced by a new instance.","triggerScenarios":"Calling run() twice on a Runner used in a 'with' block after the block exited: with Runner() as r: r.run(a); r.run(b). Also storing a Runner across requests and reusing it after an explicit r.close() in error handling, or calling get_loop() post-close.","commonSituations":"Refactoring code from a single asyncio.run() to a shared Runner and keeping the 'with' scoping; error paths that close() the runner then fall through to retry logic that runs again; caching a Runner as a module global while a shutdown hook closes it.","solutions":["Create a fresh Runner for each batch of runs: keep runs inside one 'with' block, or construct a new Runner after close","Remove premature close() calls in error handlers; rely on context-manager exit instead","If reusing across calls, instantiate the Runner without 'with' and close it only in final shutdown, checking is_closed if available","Restructure to asyncio.run() per invocation when runner reuse is not actually needed"],"exampleFix":"# before\nwith Runner() as r:\n    r.run(first())\nr.run(second())  # Runner is closed\n# after\nwith Runner() as r:\n    r.run(first())\n    r.run(second())  # both inside the context","handlingStrategy":"validation","validationCode":"class ReusableRunner:\n    def __init__(self):\n        self._runner = None\n\n    def run(self, coro):\n        if self._runner is None or self._runner._state is _State.CLOSED:\n            self._runner = asyncio.Runner()\n        return self._runner.run(coro)","typeGuard":null,"tryCatchPattern":"try:\n    r.run(coro())\nexcept RuntimeError as e:\n    if 'Runner is closed' in str(e):\n        r = asyncio.Runner()          # recreate and retry once\n        return r.run(coro())\n    raise","preventionTips":["Keep every run() inside the Runner's with-block","Do not close() runners on per-request errors; reserve close for final shutdown","Store runners in explicit lifecycle containers rather than ad-hoc globals","Prefer one asyncio.run() per process entrypoint unless runner reuse is required"],"tags":["asyncio","lifecycle","runner","state"],"backgroundTag":null,"analyzedSha":"bc6749cc3b5ae4a5e88a6cc2d5b3bebbe354eae6","analyzedAt":"2026-08-14T22:01:13.976Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}