python/cpython · error · ValueError
Unknown thread safety level {level!r} for {name!r}. Valid le
Error message
Unknown thread safety level {level!r} for {name!r}. Valid levels: {sorted(_VALID_THREADSAFETY_LEVELS)} What it means
Runner.run() accepts exactly a coroutine; other awaitables (Futures, objects with __await__) are tolerated only by auto-wrapping, and everything else raises this TypeError. It is the same contract as loop.create_task()/asyncio.run(): you must pass something awaitable, not a plain function, coroutine function, or result value.
Source
Thrown at Doc/tools/extensions/c_annotations.py:150
"atomic",
})
def read_threadsafety_data(
threadsafety_filename: Path,
) -> dict[str, ThreadSafetyEntry]:
threadsafety_data = {}
for line in threadsafety_filename.read_text(encoding="utf8").splitlines():
line = line.strip()
if not line or line.startswith("#"):
continue
# Each line is of the form: function_name : level : [comment]
parts = line.split(":", 2)
if len(parts) < 2:
raise ValueError(f"Wrong field count in {line!r}")
name, level = parts[0].strip(), parts[1].strip()
if level not in _VALID_THREADSAFETY_LEVELS:
raise ValueError(
f"Unknown thread safety level {level!r} for {name!r}. "
f"Valid levels: {sorted(_VALID_THREADSAFETY_LEVELS)}"
)
threadsafety_data[name] = ThreadSafetyEntry(name=name, level=level)
return threadsafety_data
def add_annotations(app: Sphinx, doctree: nodes.document) -> None:
state = app.env.domaindata["c_annotations"]
refcount_data = state["refcount_data"]
stable_abi_data = state["stable_abi_data"]
threadsafety_data = state["threadsafety_data"]
for node in doctree.findall(addnodes.desc_content):
par = node.parent
if par["domain"] != "c":
continue
if not par[0].get("ids", None):
continueView on GitHub (pinned to bc6749cc3b)
Solutions
- Call the coroutine function: runner.run(main())
- If building the awaitable dynamically, ensure the expression evaluates to a coroutine: runner.run(factory()) where factory is async def
- For plain results, wrap them: async def _wrap(): return value, then run(_wrap())
- Verify with inspect.iscoroutinefunction before passing callables through generic glue code
Example fix
# before runner.run(main) # main is a coroutine function -> TypeError # after runner.run(main()) # invoke to obtain the coroutine
Defensive patterns
Strategy: type-guard
Validate before calling
import inspect, asyncio
def run_entry(runner, main):
if inspect.iscoroutinefunction(main):
raise TypeError('call the coroutine function: pass main(), not main')
if not (asyncio.iscoroutine(main) or inspect.isawaitable(main)):
raise TypeError(f'not awaitable: {type(main).__name__}')
return runner.run(main) Type guard
def is_runnable_coroutine(obj) -> bool:
return asyncio.iscoroutine(obj) or inspect.isawaitable(obj) Try / catch
try:
runner.run(main)
except TypeError as e:
if 'awaitable is required' in str(e) and inspect.iscoroutinefunction(main):
raise TypeError('forgot parentheses: use main()') from e
raise Prevention
- Always invoke: run(main()), never run(main)
- Annotate entry helpers as (coro: Coroutine) so mypy flags misuse
- Wrap non-coroutine results in an async def before run()
- Beware 'x or default_main' patterns that can pass a function reference
When it happens
Trigger: Passing a coroutine function without calling it: runner.run(main) instead of runner.run(main()); passing a lambda/def result (e.g. run(get_value())) where get_value is sync; passing a Task or Future created elsewhere (older semantics) or a plain object like a string/None.
Common situations: Missing parentheses on the main coroutine — the single most common hit; passing an already-awaited coroutine's cached result; helpers that accept 'main or default_main' where the default is a function reference not an invocation; passing functools.partial of an async def without calling it (partial is not awaitable unless it yields a coroutine).
Related errors
- Not implemented
- Object type mismatch in limited API annotation for {name}: {
- coroutine ignored GeneratorExit
- Socket cannot be of type SSLSocket
- 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/9802107c8755b001.
Report an issue: GitHub.