Ihor Ramskyi
09/26/2025, 3:21 PMprefect.exceptions.ScriptError: Script at 'main.py' encountered an exception: TypeError('Result storage configuration must be persisted server-side. Please call `.save()` on your block before passing it in.')
The error message is misleading because I already have my block set up (and it gets used fine for running the flow, the problem is with deploying. What could be the fix?
My code example:
import os
from datetime import timedelta
from prefect import flow, task
from prefect.cache_policies import INPUTS, TASK_SOURCE
from prefect.filesystems import LocalFileSystem
path_block = LocalFileSystem.load("cache-folder")
@task(
cache_policy=(INPUTS + TASK_SOURCE).configure(key_storage=os.path.join(path_block.basepath, "key_storage")),
cache_expiration=timedelta(minutes=2)
)
def t():
return "cache this pls"*1000000
@flow(result_storage=LocalFileSystem.load("cache-folder"))
def main():
t.submit().result()
if __name__ == "__main__":
main()
Marvin
09/26/2025, 3:21 PMMarvin
09/26/2025, 3:32 PMLocalFileSystem.load("cache-folder")
succeeds in your current profile/workspace, so the flow runs and writes results/caches where you expect.
- During deploy, Prefect imports your module to discover flows. At import time, it tries to validate result_storage
. If the deploy process is not connected to the same server/workspace (or cannot resolve the block), Prefect treats the storage as “not persisted” and raises:
TypeError: Result storage configuration must be persisted server-side…
This is a known pain point with the eager validation in v3; see issue #16641 and a related report issue #16229.
Fix options (pick one)
1) Reference the storage block by slug (preferred for deployments)
- Don’t call .load()
at import time. Pass the block slug instead; Prefect will resolve it later.
# Use the saved block slug from the UI, e.g. "local-file-system/cache-folder"
@flow(result_storage="local-file-system/cache-folder")
def main():
t.submit().result()
2) Set the default result storage via settings and drop result_storage from the decorator
- Set an environment variable for your deployment/worker:
export PREFECT_RESULTS_DEFAULT_STORAGE_BLOCK="local-file-system/cache-folder"
- Then just:
@flow
def main():
t.submit().result()
Docs reference: Settings reference (see PREFECT_RESULTS_DEFAULT_STORAGE_BLOCK)
3) Ensure the deploy process is using the same profile/workspace that contains your block
- In the shell where you run prefect deploy
, verify:
- prefect profile ls
— confirm the active profile
- prefect cloud workspace ls
(Cloud) — confirm the active workspace
- prefect block ls
— confirm the block is visible
- If needed, log in and set the workspace before deploying:
- prefect cloud login
and prefect cloud workspace set <org>/<workspace>
About your cache key storage
- Your cache_policy
path is evaluated at import time too. Since you don’t need a saved block for key storage, avoid calling .load()
there. Use an absolute path or env var instead:
KEY_DIR = os.environ.get("PREFECT_CACHE_KEY_DIR", "/path/to/cache-folder/key_storage")
@task(
cache_policy=(INPUTS + TASK_SOURCE).configure(key_storage=KEY_DIR),
cache_expiration=timedelta(minutes=2),
)
def t():
return "cache this pls" * 1000000
- Note: LocalFileSystem caches are per-machine. If workers run on different machines, they won’t share cache. For shared caching, consider a remote filesystem block instead.
Putting it together (safe for deploy)
from datetime import timedelta
from prefect import flow, task
from prefect.cache_policies import INPUTS, TASK_SOURCE
import os
KEY_DIR = os.environ.get("PREFECT_CACHE_KEY_DIR", "/absolute/path/to/cache-folder/key_storage")
@task(
cache_policy=(INPUTS + TASK_SOURCE).configure(key_storage=KEY_DIR),
cache_expiration=timedelta(minutes=2),
)
def t():
return "cache this pls" * 1000000
@flow(result_storage="local-file-system/cache-folder") # saved block slug
def main():
t.submit().result()
if __name__ == "__main__":
main()
Docs for reference:
- Filesystems: prefect.filesystems
- Caching: Caching concepts
Quick check: Are you deploying from the same profile/workspace where the “cache-folder” block is saved? If not, switching the profile/workspace or using the block slug/env var above should resolve this.