python/cpython · error · LookupError
{name} is not part of stable ABI. Document it as `c:macro::`
Error message
{name} is not part of stable ABI. Document it as `c:macro::` rather than `corresponding-type-slot::`. What it means
asyncio.run() must be the entry point that creates and owns the event loop; it cannot run while a loop is already running in the current thread. The guard uses events._get_running_loop() and deliberately fails fast with a short traceback. Nesting asyncio.run() inside a coroutine is unsupported because the outer loop cannot be suspended behind a second loop.
Source
Thrown at Doc/tools/extensions/c_annotations.py:428
If there is no corresponding field, these should be documented as normal
macros.
"""
has_content = False
required_arguments = 1
optional_arguments = 0
def run(self) -> list[nodes.Node]:
name = self.arguments[0]
state = self.env.domaindata["c_annotations"]
stable_abi_data = state["stable_abi_data"]
try:
record = stable_abi_data[name]
except LookupError as err:
raise LookupError(
f"{name} is not part of stable ABI. "
+ "Document it as `c:macro::` rather than "
+ "`corresponding-type-slot::`."
) from err
annotation = _stable_abi_annotation(record, is_corresponding_slot=True)
node = nodes.paragraph()
content = [
".. c:namespace:: NULL",
"",
".. c:macro:: " + name,
" :no-typesetting:",
]
self.state.nested_parse(StringList(content), 0, node)
node.insert(0, annotation)
return [node]
View on GitHub (pinned to bc6749cc3b)
Solutions
- Inside async code, await the coroutine directly instead of asyncio.run()
- Give the library an async API: async def do_async() plus a thin sync wrapper do() = asyncio.run(do_async()) that is only called from sync contexts
- In Jupyter, use top-level await or nest_asyncio.apply() as an environment-specific workaround
- Restructure so asyncio.run() appears exactly once at the true process entrypoint
Example fix
# before
async def handler():
result = asyncio.run(fetch()) # RuntimeError
# after
async def handler():
result = await fetch() Defensive patterns
Strategy: validation
Validate before calling
import asyncio, asyncio.events as events
def run_bridge(coro_factory):
try:
events.get_running_loop()
except RuntimeError:
return asyncio.run(coro_factory())
raise RuntimeError('already inside a loop: await the coroutine instead') Try / catch
try:
asyncio.run(main())
except RuntimeError as e:
if 'running event loop' in str(e):
raise RuntimeError('await main() directly here') from e
raise Prevention
- Keep exactly one asyncio.run() at the process entrypoint
- In Jupyter, use top-level await or nest_asyncio deliberately
- Audit sync libraries called from async handlers for hidden asyncio.run
- Convert library internals to native async coroutines
When it happens
Trigger: Calling asyncio.run(...) inside an async def, inside a loop callback, inside a Jupyter notebook cell, or from a thread that is currently running a loop. Also transitively: a sync library function that internally calls asyncio.run() being invoked from async code.
Common situations: Jupyter/IPython (running loop) calling libraries that use asyncio.run() for bridging; test code mixing pytest.sync execution with asyncio.run inside fixtures; web frameworks (FastAPI handlers) calling sync SDKs that call asyncio.run(); migration from threads to asyncio where nested entrypoints remain.
Related errors
- {htmldir!r} is not a Sphinx HTML output directory
- deprecated-removed:: second argument cannot be `next`
- no running event loop
- There is no current event loop in thread %r.
- loop must be an instance of AbstractEventLoop or None, not '
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/db33fc88348a7f4d.
Report an issue: GitHub.