<@ULVA73B9P> More trouble with async. I get base_e...
# ask-marvin
r
@Marvin More trouble with async. I get base_events.py2047 RuntimeWarning: coroutine 'sync_compatible.<locals>.coroutine_wrapper.<locals>.ctx_call' was never awaited handle = None # Needed to break cycles when an exception occurs. but I don't know what or where is causing that. This is from a Prefect 3.7.0 async flow
m
thought for 184.8 seconds
That warning usually means something Prefect made “sync-or-async” was called inside your async flow without
await
. The specific coroutine name is a strong clue:
Copy code
text
coroutine 'sync_compatible.<locals>.coroutine_wrapper.<locals>.ctx_call' was never awaited
That comes from Prefect’s
@sync_compatible
wrapper. In an
async def
flow, some Prefect APIs return a coroutine and must be awaited, even if the same API can be called without
await
in a sync flow. Most likely cause Inside your async flow, you have something like one of these:
Copy code
python
@flow
async def my_flow():
    x = Variable.get("foo")          # should be: await Variable.get("foo")
    b = Block.load("my-block")       # should be: await Block.load("my-block")
Other Prefect APIs with this behavior can do the same thing. A Prefect PR/discussion specifically calls out this pattern for
Variable.get
in sync vs async contexts: clarify variables docs Example from that PR:
Copy code
python
@flow
async def af():
    print(await Variable.get("answer"))
How to find the exact line The warning often points at asyncio internals instead of your code, so the best trick is to turn it into an error:
Copy code
bash
python -W error::RuntimeWarning your_script.py
That usually gives you a traceback to the offending line. You can also grep your async flow for Prefect calls that are not awaited, especially: -
Variable.get(...)
-
Block.load(...)
- other Prefect methods you’re calling directly inside
async def
What to change If your flow is async:
Copy code
python
from prefect import flow
from prefect.variables import Variable

@flow
async def my_flow():
    value = await Variable.get("foo")
    print(value)
And similarly for blocks:
Copy code
python
@flow
async def my_flow():
    block = await Block.load("my-block-name")
Important nuance In Prefect 3.x, some APIs are “dually contextual”: - in
def
flows/tasks: call normally - in
async def
flows/tasks:
await
them So code that works fine in a sync flow can emit this warning when moved into an async flow. What I found in Prefect’s implementation This comes from Prefect’s
sync_compatible
utility, which returns a coroutine in async contexts. If that return value is not awaited, Python emits exactly the warning you’re seeing. I didn’t find a GitHub discussion specifically for this exact warning string, but the behavior is consistent with the documented sync/async
Variable.get
pattern and the underlying implementation. Useful links - Variables sync/async clarification: PR 15233 - Related issue touching
sync_compatible
behavior/docs: Issue 16229 If you want, paste the async flow code (or even just the body of the flow), and I can help spot the exact call that needs
await
.
r
@Marvin how can I do your python -W error::RuntimeWarning but from a uv run ?
m
thought for 5.5 seconds
Yep — pass the warning flag through to Python via
uv run
. Use:
Copy code
bash
uv run python -W error::RuntimeWarning your_script.py
If you normally run a module:
Copy code
bash
uv run python -W error::RuntimeWarning -m your_module
If you want all runtime warnings promoted to errors during a Prefect invocation, you can also use the env var form:
Copy code
bash
PYTHONWARNINGS=error::RuntimeWarning uv run python your_script.py
On Windows PowerShell:
Copy code
powershell
$env:PYTHONWARNINGS="error::RuntimeWarning"
uv run python your_script.py
If you're launching via `prefect run`/`prefect deploy`/worker execution, it gets a little trickier because the warning setting needs to be present in the actual Python process running your flow. For local reproduction,
uv run python ...
is the easiest path. If you want, I can help translate this to the exact command you use today to start the flow.
r
@Marvin adding the runtimewarning bit didn't show anything new when I ran the script. also when I read the script I don't see any loads or gets that are do not have a wait
m
thought for 18.6 seconds
If
-W error::RuntimeWarning
didn’t surface anything new, that usually means one of two things: 1. the warning is emitted during shutdown / cleanup, where the traceback is not very helpful, or 2. the un-awaited coroutine is being created indirectly inside another call, not at an obvious
Variable.get
/
Block.load
line. A few practical ways to narrow it down: 1. Enable asyncio debug mode too Run with both:
Copy code
bash
PYTHONASYNCIODEBUG=1 PYTHONWARNINGS=default uv run python your_script.py
Or:
Copy code
bash
uv run python -X dev your_script.py
-X dev
turns on extra runtime checks and often gives better asyncio diagnostics. On PowerShell:
Copy code
powershell
$env:PYTHONASYNCIODEBUG="1"
$env:PYTHONWARNINGS="default"
uv run python your_script.py
2. Look for “hidden” Prefect calls, not just
load
/
get
The warning is from Prefect’s
sync_compatible
wrapper, so the culprit may be another Prefect API that behaves sync in
def
code and async in
async def
code. Things to inspect in your async flow/tasks: - calling a Prefect-decorated task but not awaiting the result -
task.submit(...)
/
task.map(...)
patterns -
pause_flow_run
,
resume_flow_run
, or other orchestration helpers - block methods besides
load
- variable / secret / artifact helpers - anything returning a state or future that you then use like a plain value For example, these can be suspicious depending on context:
Copy code
python
result = my_task()
future = my_task.submit()
values = my_task.map(items)
Inside async flows, the exact correct pattern depends on whether you want direct execution, submitted futures, or mapped results. 3. Check async tasks too, not just the flow body A missing
await
inside an
@task async def ...
can surface while the async flow is running, even if the flow itself looks correct. 4. Add temporary probes around suspicious calls If the warning doesn’t point to a line, binary-search your flow:
Copy code
python
@flow
async def my_flow():
    print("before step 1")
    await step_1()
    print("after step 1")

    print("before step 2")
    x = await maybe_prefect_call()
    print("after step 2", x)
Or around direct calls:
Copy code
python
print("before foo")
result = foo(...)
print("after foo", type(result), result)
If you ever see the returned object is a coroutine, that’s your smoking gun. You can also test explicitly:
Copy code
python
import inspect

res = some_call(...)
print(type(res), inspect.iscoroutine(res))
If that prints
True
, it needed
await
. 5. Watch for helper functions wrapping Prefect APIs Sometimes the missing await is hidden in your own utility layer:
Copy code
python
def get_config():
    return Variable.get("x")   # bad if called from async context
Then later:
Copy code
python
@flow
async def my_flow():
    cfg = get_config()
At a glance the flow looks fine, but
get_config()
returned a coroutine. In async code that helper would need to be:
Copy code
python
async def get_config():
    return await Variable.get("x")
6. The warning may come from import-time or setup code If you have module-level code like:
Copy code
python
MY_VAR = Variable.get("x")
MY_BLOCK = Block.load("y")
that can also behave oddly depending on where/how the module is imported. 7. Use
tracemalloc
for better warning origins
Sometimes Python will show where the coroutine was allocated if
tracemalloc
is enabled:
Copy code
bash
uv run python -X tracemalloc=25 -W default your_script.py
Combined with
PYTHONASYNCIODEBUG=1
, this can help. Most likely culprit pattern if “nothing obvious” is visible The most common next-level cause is: - a helper function that calls a sync-compatible Prefect API - a task invocation pattern in async code - a Prefect utility call buried in setup code, callbacks, hooks, or a custom class method If you paste: - the async flow - any async tasks it calls - any helper functions that touch Prefect APIs I can usually spot the likely source pretty quickly.