Syméon del Marmol
08/29/2025, 1:04 PMMarvin
08/29/2025, 1:04 PMMarvin
08/29/2025, 1:06 PMfuture.result()
(or aresult()
) on a background task created with .delay()
while it hasn’t completed yet. If the wait ends early (e.g., no active task worker, connectivity issue, or other interruption), the error used to say “None seconds”.
- Less commonly: transient API/server connectivity issues while waiting for completion.
References
- Issue: Task run did not complete within None seconds
- Fix: Fix confusing “None seconds” timeout error message
How to fix or work around it
1) Upgrade Prefect to the latest 3.x release. The message was fixed so you’ll get a clearer error (and recent versions include other robustness improvements).
pip install -U prefect
2) If you’re using background tasks, make sure a task worker is actually running and subscribed before you call `.result()`:
- Run the task worker (e.g., via .serve()
in a separate process) so your delayed tasks can be picked up.
- Example:
from prefect import task
@task
def background_task():
return "done"
# In one process
if __name__ == "__main__":
background_task.serve() # runs the task worker
Then in your flow/process:
f = background_task.delay()
res = f.result(timeout=600) # optionally set an explicit timeout
3) If you truly want fire-and-forget, don’t call .result()
at all:
background_task.delay() # enqueue and move on
4) If you need to wait indefinitely, result()
with no timeout should block; if you still see errors after upgrading, try an explicit timeout and share logs:
res = f.result(timeout=600)
Can you share:
- Your Prefect version (prefect version
)
- Whether you’re using `.delay()`/background tasks vs. normal in-flow tasks
- A minimal snippet of how you call .result()
and how/where your worker is running
With that, I can give more targeted guidance.Syméon del Marmol
08/29/2025, 1:11 PM3.4.14
of prefect
• Indeed I'm using background tasks and calling delay() followed by result_async()
to await results in parallel:
parallel_tasks = [
import_table_parallel.with_options(
name=f"import_table_{table}",
).delay(table)
for table, json_fields in tables
]
# Wait for all results of parallel tasks
results = await asyncio.gather(
*[task.result_async(raise_on_failure=False) for task in parallel_tasks]
)
Marvin
08/29/2025, 1:11 PMMarvin
08/29/2025, 1:16 PM.delay()
then awaiting with .result_async()
in parallel. There’s a known bug in this area where a wait can raise a TimeoutError with the confusing text “within None seconds” even when no timeout was set. It’s just the message format; it does not mean you configured a timeout of None
.
- Issue: Task run did not complete within None seconds
- Fix (message clarity): Fix confusing “None seconds” timeout error message
Important: raise_on_failure=False
will not suppress a TimeoutError — that flag only affects raising when the task run ends in a failure state. A TimeoutError is thrown by the “waiting” layer itself.
What to do next
1) Upgrade to the latest Prefect 3.x
This includes the clearer error message and other robustness improvements.
pip install -U prefect
2) Double‑check your background task worker is running and subscribed before you call .delay()
Make sure the worker process for import_table_parallel
is already up and connected to the same API as your flow process, e.g.:
from prefect import task
@task
def import_table_parallel(table):
...
if __name__ == "__main__":
# run this in a separate process/terminal before starting the flow
import_table_parallel.serve()
If the worker isn’t running or can’t reach the API, result_async()
may give you a wait error even though no explicit timeout was set.
3) Add an explicit timeout while you diagnose and don’t let a single wait cancel the whole group
This gives you clearer control and visibility over which tasks are slow or not being picked up. Also set return_exceptions=True
on gather
so one exception doesn’t cancel the rest.
results = await asyncio.gather(
*[
f.result_async(timeout=600, raise_on_failure=False)
for f in parallel_tasks
],
return_exceptions=True,
)
Then inspect any exceptions in results
to see which future(s) are timing out. You can also log f.id
and check the run in the UI to confirm it’s getting picked up.
4) If you don’t need distributed/background execution, consider in‑flow parallelism
If your goal is just parallel CPU work on the same machine, using a task runner avoids the background worker path:
- Process-based: ProcessPoolTaskRunner
- Thread-based: ThreadPoolTaskRunner
Docs: Task runners in Prefect 3
5) If it still happens after upgrading
Please share:
- How you start the worker (code snippet/logs) and confirm it’s running before the flow kicks off
- Whether you’re on Prefect Cloud or self-hosted server
- The exact stack trace of the TimeoutError from the flow side
- One example task run ID that “timed out” along with the worker logs for that run
I can help pinpoint whether it’s a worker subscription/visibility issue, a connectivity blip, or something else in your setup.Syméon del Marmol
08/29/2025, 1:22 PMMarvin
08/29/2025, 1:22 PMMarvin
08/29/2025, 1:22 PM@task(timeout_seconds=...)
defaults to None
(no engine-enforced timeout) if you don’t set it. See API: https://docs-3.prefect.io/v3/api-ref/python/prefect-tasks/#prefect.tasks.task
- Waiting for results: future.result_async(timeout=...)
also defaults to None
, meaning it will wait indefinitely unless you supply a timeout. See API: https://docs-3.prefect.io/v3/api-ref/python/prefect-futures/
If you’re seeing “did not complete within None seconds,” that’s a known confusing error message when waiting on background tasks; it’s not indicating that a hidden timeout was applied. Details: issue and fix PR.
If you want a guardrail, set an explicit timeout when awaiting:
results = await asyncio.gather(
*[f.result_async(timeout=600, raise_on_failure=False) for f in parallel_tasks],
return_exceptions=True,
)
Otherwise, with the defaults, Prefect won’t time out your background tasks or the wait for their results.