<@ULVA73B9P> why some tasks get cached status if I...
# marvin-ai
i
@Marvin why some tasks get cached status if I never specified any cache policy for them or for the flow in general?
m
thought for 360.3 seconds
Yes — in Prefect 3, tasks can be Cached even if you never explicitly set a cache policy. The short version is: Prefect gives tasks a default cache policy in many cases, and when a matching prior result exists, the task run is marked
Cached
instead of re-executing.
A few key points: ### Why this happens By default, Prefect tasks may use an implicit cache policy equivalent to hashing things like: - task inputs - task source - run context / run id If Prefect finds an existing persisted result for that cache key, it can reuse it and mark the task run as
Cached
. ### Important detail: caching depends on persisted results A task can only really be reused if its result was persisted somewhere. Prefect can automatically enable result persistence when caching-related settings are in play, even if you didn’t manually configure a cache policy. So the behavior is often: 1. task gets a default cache policy 2. result persistence is enabled 3. a later matching run finds the stored result 4. task enters
Cached
### Common reasons users see this unexpectedly - You’re using plain
@task
and assuming “no cache policy” means “no caching” - A prior run already produced a persisted result for the same task/input combination - Global settings are enabling caching or result persistence - Task workers / orchestration behavior are deduplicating matching task runs ### How to turn it off explicitly If you never want a task to cache, set `NO_CACHE`:
Copy code
from prefect import task
from prefect.cache_policies import NO_CACHE

@task(cache_policy=NO_CACHE, persist_result=False)
def my_task(x):
    return x * 2
If you want to inspect what Prefect thinks your task is configured with:
Copy code
print(my_task.cache_policy)
print(my_task.persist_result)
### Things to check in your environment Look for settings like: -
PREFECT_TASKS_DEFAULT_NO_CACHE
-
PREFECT_TASKS_DISABLE_CACHING
-
PREFECT_RESULTS_PERSIST_BY_DEFAULT
These can change the default behavior across your environment. ### Docs - Caching docs - Result persistence docs - Cache policies API If you want, I can also help you diagnose a specific task and show exactly why it was marked
Cached
from its config/code.
i
@Marvin how do I check if there were cached task runs across all the flow runs that I might be not aware of?
m
thought for 155.4 seconds
Yes — the best way is to query task runs whose state name is
Cached
across your workspace. Important nuance: -
Cached
is typically a state name - the underlying state type is usually still
COMPLETED
So you want to filter by state name =
Cached
, not just by completed runs. ### Easiest: CLI Try:
Copy code
prefect task-run ls --state Cached --limit 100
If you want machine-readable output:
Copy code
prefect task-run ls --state Cached --output json
Then inspect any suspicious one:
Copy code
prefect task-run inspect <TASK_RUN_ID>
I tried to verify the exact CLI help, but the command help timed out in my environment, so if your local CLI version differs slightly, run:
Copy code
prefect task-run --help
prefect task-run ls --help
### Python client: check across all flow runs This is more reliable if you want a complete audit:
Copy code
from prefect.client.orchestration import get_client
from prefect.client.schemas.filters import (
    TaskRunFilter,
    TaskRunFilterState,
    TaskRunFilterStateName,
)
from prefect.client.schemas.sorting import TaskRunSort
import asyncio

async def find_cached_task_runs(limit=200):
    task_run_filter = TaskRunFilter(
        state=TaskRunFilterState(
            name=TaskRunFilterStateName(any_=["Cached"])
        )
    )

    async with get_client() as client:
        runs = await client.read_task_runs(
            task_run_filter=task_run_filter,
            limit=limit,
            sort=TaskRunSort.EXPECTED_START_TIME_DESC,
        )

    for tr in runs:
        state = tr.state
        details = state.state_details if state else None
        print(f"task_run_id={tr.id}")
        print(f"task_name={tr.name}")
        print(f"flow_run_id={tr.flow_run_id}")
        print(f"state_name={state.name if state else None}")
        print(f"state_type={state.type if state else None}")
        print(f"cache_key={getattr(details, 'cache_key', None)}")
        print(f"cache_expiration={getattr(details, 'cache_expiration', None)}")
        print("-" * 60)

asyncio.run(find_cached_task_runs())
### Narrow it to one flow run
Copy code
from prefect.client.orchestration import get_client
from prefect.client.schemas.filters import (
    TaskRunFilter,
    TaskRunFilterState,
    TaskRunFilterStateName,
    TaskRunFilterFlowRunId,
)
import asyncio

async def find_cached_for_flow_run(flow_run_id):
    task_run_filter = TaskRunFilter(
        state=TaskRunFilterState(
            name=TaskRunFilterStateName(any_=["Cached"])
        ),
        flow_run_id=TaskRunFilterFlowRunId(any_=[flow_run_id]),
    )

    async with get_client() as client:
        runs = await client.read_task_runs(task_run_filter=task_run_filter)

    return runs
### What to look for For each cached task run, inspect: -
flow_run_id
-
state.name
→ should be
Cached
-
state.state_details.cache_key
-
state.state_details.cache_expiration
If you see the same
cache_key
appearing repeatedly across different flow runs, that’s a strong sign Prefect has been reusing prior results. ### In the UI You can also check in Prefect UI by filtering task runs by state
Cached
. That uses the same underlying API filter. ### Docs - Caching docs - Result persistence docs If you want, I can also give you a small script that summarizes: - which tasks were cached most often - which flow runs they appeared in - which cache keys were reused across runs
i
@Marvin I noticed that the cached task run happened to be the first run of that task in general. How could that happen?
m
thought for 315.2 seconds
Yes — that can happen, and it usually means it was only the first visible task run record, not the first time Prefect saw that cache key. The important distinction is: - first task run row you noticedfirst result stored for that cache key ### Most likely explanations #### 1. There was already a stored result for the same cache key Prefect caching is based on the cache key, not “has this exact task run ID executed before”. So if Prefect found an existing persisted result with the same key, it can immediately mark this run as
Cached
even if this is the first task run you’re looking at in the UI. That prior cached result could have come from: - an earlier flow run - an earlier run of the same code with the same inputs - a previous deployment / renamed task / recreated task definition - another environment sharing the same result storage #### 2. The task was called more than once with identical inputs in the same flow If the task is invoked twice in one flow run with the same effective cache key, the second invocation can show up as
Cached
. #### 3. A concurrent run “won the race” If two matching task executions started around the same time, one may complete and persist the result first, and the other can then reuse it and show
Cached
. #### 4. A previous persisted result existed even if the prior run record is gone or not obvious Sometimes the persisted artifact/result exists even when the older run is not easy to find anymore, for example due to: - different workspace visibility/context - old run history being harder to locate - result storage surviving longer than what you’re viewing in the UI ### Why this is possible technically In Prefect 3, the engine/orchestration checks for an existing result for the computed cache key before actually executing the task body. If it finds one, it can: - skip execution - read the result - set the state name to
Cached
So
Cached
does not mean “this task run itself executed before”. It means “Prefect found a reusable result for this cache key”. ### How to debug this Inspect the cached task run and compare: -
task_run.state.state_details.cache_key
-
task_run.flow_run_id
- task inputs - task source/code version - result storage location You want to find other task runs with the same cache key. Here’s a useful script:
Copy code
from prefect.client.orchestration import get_client
from prefect.client.schemas.filters import TaskRunFilter
import asyncio

async def inspect_task_run(task_run_id):
    async with get_client() as client:
        tr = await client.read_task_run(task_run_id)
        print(f"task run: {tr.id}")
        print(f"name: {tr.name}")
        print(f"flow_run_id: {tr.flow_run_id}")
        print(f"state: {tr.state.name} / {tr.state.type}")

        details = tr.state.state_details
        cache_key = getattr(details, "cache_key", None)
        print(f"cache_key: {cache_key}")
        print(f"cache_expiration: {getattr(details, 'cache_expiration', None)}")

asyncio.run(inspect_task_run(\"YOUR_TASK_RUN_ID\"))
If you can access your API/database more broadly, the ideal audit is: - get the
cache_key
from the cached run - search for other task runs sharing that key - check whether another run completed earlier ### How to force fresh execution temporarily If you want to test whether cache reuse is the cause:
Copy code
from prefect import task
from prefect.cache_policies import NO_CACHE

@task(cache_policy=NO_CACHE, persist_result=False)
def my_task(x):
    return x * 2
Or use cache refresh if that fits your setup. ### Docs - Caching docs - Result persistence docs If you want, I can help you write a script that: 1. takes one cached task run ID 2. extracts its
cache_key
3. searches your recent task runs for probable source runs that produced that cached result.
i
@Marvin do these settings relate to the cache issue I'm asking you about?
Copy code
tasks: {
  "refresh_cache": false,
  "default_no_cache": false,
m
thought for 11.9 seconds
Yes — they are directly related. ### What they mean ####
tasks.refresh_cache: false
This means Prefect is allowed to reuse existing cached results. -
false
= use cache if a matching cache key exists -
true
= bypass existing cache and force fresh execution So this setting absolutely affects why you may see
Cached
. ####
tasks.default_no_cache: false
This means Prefect does not disable default caching behavior for tasks. In practice, this allows Prefect’s default task cache policy behavior to remain active. -
false
= default caching is still allowed -
true
= tasks default to no caching unless you explicitly enable it So this is also directly related. ### In your case With:
Copy code
tasks: {
  "refresh_cache": false,
  "default_no_cache": false,
}
you are basically telling Prefect: - do not force recomputation - do not disable task caching by default That is consistent with tasks unexpectedly entering
Cached
. ### If you want to stop this behavior globally You’d generally want:
Copy code
tasks: {
  "refresh_cache": true,
  "default_no_cache": true,
}
But note the difference: -
refresh_cache: true
= ignore existing cache and recompute -
default_no_cache: true
= don’t apply default caching behavior to tasks in the first place Usually, if your goal is “I never want implicit caching”,
default_no_cache: true
is the more important one. ### Docs - Caching docs - Result persistence docs If you want, I can help map those YAML-style settings to the exact environment variables / Python settings names too.