oleander-whirlwind
06/05/2026, 9:30 AMMarvin
06/05/2026, 9:30 AMMarvin
06/05/2026, 9:33 AMretry_condition_fn is preventing the retry.
In Prefect 3.x, a task timeout can be retried if:
- retries > 0
- the retry condition allows it
- the flow itself hasn’t timed out/stopped first
What you’re seeing:
Finished in state TimedOut('Task run exceeded timeout of 3600.0 second(s)', type=FAILED)
...
Flow run failed due to timeout: TaskRunTimeoutError(...)
does not mean retries are impossible. It means the task ended in a failed timeout state, and Prefect did not transition it to AwaitingRetry / Retrying.
The main reasons are:
1. retry_condition_fn returned False for the timeout
Your function gets the failed state and decides whether to retry. If it inspects the exception and excludes TaskRunTimeoutError, Prefect will fail the task immediately.
A timeout-safe version looks like:
python
from prefect.task_engine import TaskRunTimeoutError
def retry_download_slice(task, task_run, state):
try:
state.result()
except TaskRunTimeoutError:
return True
except Exception:
return True
return True
If you only want to retry some exceptions, make sure timeout is included explicitly.
2. retry_condition_fn itself is erroring
If that function raises an exception while evaluating the failed state, Prefect will generally skip retrying.
For example, this is fragile:
python
def retry_download_slice(task, task_run, state):
exc = state.result()
return exc.status_code != 404
because state.result() raises the underlying exception instead of returning a neat object.
Safer pattern:
python
from prefect.task_engine import TaskRunTimeoutError
def retry_download_slice(task, task_run, state):
try:
state.result()
except TaskRunTimeoutError:
return True
except SomePermanentError:
return False
except Exception:
return True
return True
3. A flow-level timeout may be confusing the logs
Your log includes:
prefect.flow_runs
Flow run failed due to timeout: TaskRunTimeoutError(...)
That message can be misleading. It may just mean the flow failed because the task timed out. But if your flow also has a timeout, that can stop retries entirely.
So check whether your flow decorator also has something like:
python
@flow(timeout_seconds=...)
If the flow times out before the retry delay / next attempt, the task will not retry.
4. Sync blocking work can make timeout behavior tricky
If this task is a synchronous task doing blocking I/O or long-running C/library calls, timeout handling may not interrupt it as cleanly as expected depending on execution context. But in your case, since the task did end as TimedOut, the more likely issue is still the retry condition.
What I’d try first
Temporarily remove retry_condition_fn and see whether the timeout retries:
python
@task(
retries=3,
retry_delay_seconds=[10, 30, 60],
timeout_seconds=3600,
)
def download_interval(...):
...
If that retries, then the problem is definitely inside retry_download_slice.
Then reintroduce a minimal condition:
python
from prefect.task_engine import TaskRunTimeoutError
def retry_download_slice(task, task_run, state):
try:
state.result()
except TaskRunTimeoutError:
return True
except Exception:
return True
return True
Recommended debugging step
Add logging inside `retry_download_slice`:
python
from prefect.logging import get_run_logger
from prefect.task_engine import TaskRunTimeoutError
def retry_download_slice(task, task_run, state):
logger = get_run_logger()
try:
state.result()
except TaskRunTimeoutError as exc:
<http://logger.info|logger.info>(f"Retrying after timeout: {exc}")
return True
except Exception as exc:
<http://logger.info|logger.info>(f"Retry condition saw exception: {type(exc).__name__}: {exc}")
return True
return TrueMarvin
06/05/2026, 9:33 AMretry_download_slice function here and I can tell you exactly why the timeout is not being retried.