Short version: your retry probably ran in a different execution mode than the first attempt. Prefect will re-create the EngineContext on each attempt and may swap in a different task runner when the environment changes—most notably when a Task Worker is involved.
Likely reasons you’re seeing a different class:
- A Task Worker picked up the retry: When tasks are executed by a Task Worker, Prefect uses PrefectTaskRunner to submit tasks to that worker. If the first attempt ran locally (ThreadPoolTaskRunner) and the retry landed where a Task Worker is available, you’ll see PrefectTaskRunner on the retry.
- Different infrastructure/worker picked up the retry: Retries can be scheduled to a different machine or process than the original attempt. If that environment has different capabilities (e.g., task worker running vs not), the runner class can differ.
- Fresh attempt, fresh runner: Each flow attempt creates a new EngineContext and runner; what gets instantiated depends on the runtime conditions of that attempt, not the previous one.
What won’t cause it:
- Passing a class vs an instance for task_runner. Prefect will instantiate/duplicate your runner per attempt; that alone doesn’t change the class.
How to confirm:
- Log the runner each attempt:
- from prefect.context import get_run_context
- tr = get_run_context().task_runner
-
logger.info(f"Task runner: {tr} ({type(tr).
name})")
- Check whether a Task Worker is running in the environment that executed the retry.
How to keep it consistent:
- If you want local threaded execution, don’t run a Task Worker in the environment executing your flow attempts; Prefect will then honor ThreadPoolTaskRunner.
- If you want distributed task execution, expect PrefectTaskRunner.
Docs for reference:
- Task runners:
https://docs-3.prefect.io/v3/concepts/task-runners
- Runtime context:
https://docs-3.prefect.io/v3/concepts/runtime-context
- Retries:
https://docs-3.prefect.io/v3/how-to-guides/workflows/retries
If you can share how you’re running the flow (local vs deployment), whether a Task Worker is running, and the runner class you see on each attempt, I can pinpoint the exact cause instead of grumbling about it abstractly.