Yu(Irene) Chen
03/05/2026, 2:54 AMprocess_futures = process_attachment.map(unmapped(output_dir), total_files)
cleanup_futures = clearup.map(total_files, wait_for=process_futures)Marvin
03/05/2026, 2:54 AMMarvin
03/05/2026, 2:57 AMprocessed = process_attachment.map(unmapped(output_dir), total_files)
cleanup = clearup.map(total_files, processed) # each cleanup[i] depends on processed[i]
- Or use submit in a zip to set per-item wait_for:
processed = process_attachment.map(unmapped(output_dir), total_files)
cleanup = [clearup.submit(f, wait_for=[pf]) for f, pf in zip(total_files, processed)]
Note: If any process_attachment fails, the dependent clearup runs will not start and will reflect the upstream failure.Yu(Irene) Chen
03/13/2026, 8:40 AMMarvin
03/13/2026, 8:40 AMMarvin
03/13/2026, 8:47 AMimport asyncio
from collections import Counter
from datetime import datetime, timedelta, timezone
from prefect.client import get_client
from prefect.client.schemas.filters import DeploymentFilter, FlowRunFilter
async def find_duplicate_scheduled_runs(deployment_id: str, since: datetime, until: datetime):
async with get_client() as client:
runs = await client.read_flow_runs(
deployment_filter=DeploymentFilter(id={"any_": [deployment_id]}),
flow_run_filter=FlowRunFilter(
expected_start_time={"after": since, "before": until},
# consider active runs; include all if you want full history
# state_type={"values": ["SCHEDULED", "PENDING", "RUNNING", "COMPLETED", "CANCELLED", "FAILED"]}
),
limit=5000, # adjust as needed
)
# Group by exact expected_start_time
counts = Counter(r.expected_start_time for r in runs if r.expected_start_time)
duplicates = {ts: c for ts, c in counts.items() if c > 1}
return duplicates, runs
# Usage
since = datetime.now(timezone.utc) - timedelta(days=7)
until = datetime.now(timezone.utc) + timedelta(days=1)
duplicates, runs = asyncio.run(find_duplicate_scheduled_runs("<DEPLOYMENT_ID>", since, until))
for ts, c in sorted(duplicates.items()):
print(f"{ts.isoformat()} -> {c} runs")
Example: check a single timestamp for duplicates
import asyncio
from datetime import datetime, timedelta, timezone
from prefect.client import get_client
from prefect.client.schemas.filters import DeploymentFilter, FlowRunFilter
async def runs_at_time(deployment_id: str, ts: datetime, window_seconds: int = 1):
start = ts - timedelta(seconds=window_seconds)
end = ts + timedelta(seconds=window_seconds)
async with get_client() as client:
return await client.read_flow_runs(
deployment_filter=DeploymentFilter(id={"any_": [deployment_id]}),
flow_run_filter=FlowRunFilter(
expected_start_time={"after": start, "before": end},
state_type={"values": ["SCHEDULED", "PENDING", "RUNNING"]},
),
)
timestamp = datetime(2026, 3, 10, 12, 0, 0, tzinfo=timezone.utc)
runs = asyncio.run(runs_at_time("<DEPLOYMENT_ID>", timestamp, window_seconds=2))
print(f"Found {len(runs)} runs near {timestamp.isoformat()}")
Tip: If you suspect the root cause is multiple deployments for the same flow, list all deployments for that flow and check which have schedules enabled. In the UI, go to the Flow’s Deployments tab and look for multiple active deployments with schedules. In code, you can read all deployments and filter by d.flow_id or d.flow_name and inspect d.schedules.
How to prevent overlaps going forward
- Use a deployment-level concurrency limit of 1. This enforces that only one run from that deployment can execute at a time.
- Choose the right collision strategy:
- ENQUEUE: later runs wait for a slot (default).
- CANCEL_NEW: later runs at the same time are immediately cancelled (useful if you must never overlap executions).
Example: set a single-active-run policy when deploying
```Marvin
03/13/2026, 8:47 AMYu(Irene) Chen
03/13/2026, 8:47 AMMarvin
03/13/2026, 8:48 AMMarvin
03/13/2026, 8:49 AMprefect flow-run ls --flow-name "<FLOW_NAME>" --state SCHEDULED --limit 200
- If you’re not sure which deployments exist for that flow (to explain duplicates), list them:
prefect deployment ls --flow-name "<FLOW_NAME>"
Notes:
- --flow-name aggregates runs from all deployments of that flow.
- Increase --limit as needed to see more future scheduled runs.
- If you want to narrow by a specific deployment, the flow-run CLI doesn’t expose a deployment filter—list runs and then grep for the deployment name shown in the output.
Do you want only future scheduled runs, or all scheduled runs (past and future)?Yu(Irene) Chen
03/13/2026, 8:53 AMMarvin
03/13/2026, 8:53 AMMarvin
03/13/2026, 8:56 AMimport asyncio
from uuid import UUID
from prefect.client.orchestration import PrefectClient
async def pause_schedule(deployment_id: str, schedule_id: str):
async with PrefectClient() as client:
await client.update_deployment_schedule(
deployment_id=UUID(deployment_id),
schedule_id=UUID(schedule_id),
active=False, # False = paused, True = resumed
)
asyncio.run(pause_schedule("<DEPLOYMENT_ID>", "<SCHEDULE_ID>"))
Synchronous version
from uuid import UUID
from prefect.client.orchestration import SyncPrefectClient
def pause_schedule_sync(deployment_id: str, schedule_id: str):
with SyncPrefectClient() as client:
client.update_deployment_schedule(
deployment_id=UUID(deployment_id),
schedule_id=UUID(schedule_id),
active=False,
)
List schedules and pause by slug/name
from uuid import UUID
from prefect.client.orchestration import SyncPrefectClient
def pause_by_slug(deployment_id: str, slug: str):
with SyncPrefectClient() as client:
dep_id = UUID(deployment_id)
schedules = client.read_deployment_schedules(dep_id)
for s in schedules:
if s.slug == slug:
client.update_deployment_schedule(
deployment_id=dep_id,
schedule_id=s.id,
active=False,
)
print(f"Paused schedule: {slug}")
Pause all schedules on a deployment
from uuid import UUID
from prefect.client.orchestration import SyncPrefectClient
def pause_all_schedules(deployment_id: str):
with SyncPrefectClient() as client:
dep_id = UUID(deployment_id)
schedules = client.read_deployment_schedules(dep_id)
for s in schedules:
client.update_deployment_schedule(
deployment_id=dep_id,
schedule_id=s.id,
active=False,
)
Create a deployment with a schedule already paused
from prefect import flow
from prefect.client.schemas.actions import DeploymentScheduleCreate
from prefect.client.schemas.schedules import CronSchedule
@flow
def my_flow():
...
my_flow.deploy(
name="my-deployment",
work_pool_name="default",
schedules=[
DeploymentScheduleCreate(
schedule=CronSchedule(cron="0 9 * * *", timezone="UTC"),
active=False, # start paused
slug="morning"
)
],
)
Do you want to pause all schedules for a deployment or just a specific one? If you share the deployment name/ID (and schedule slug if applicable), I can tailor a snippet exactly to your case.Yu(Irene) Chen
03/16/2026, 3:00 AMtry:
# using sshpass to input password
result = fabric_run(
f"rsync -avz {source} {credentials['user']}@{credentials['host']}:{destination}"
f"rsync -rlvz {source} {credentials['user']}@{credentials['host']}:{destination}"
)
logger.info(f"Folder {subfolder} rsync completed successfully")
except Exception as e:
logger.error(f"Folder {subfolder} rsync failed: {e}")
return states.Failed(message=f"[TASK: {task_name}] completed unsuccessfully")Marvin
03/16/2026, 3:00 AMMarvin
03/16/2026, 3:04 AMtry/except never runs)
- Return the previously persisted result
- Mark the state as Completed with the name “Cached” (i.e., “Completed (Cached)”)
That’s why your return states.Failed(...) in the except doesn’t change the outcome when the result is coming from cache—the exception block is never reached.
How to make your failure logic take effect:
- Prefer raising an exception to fail a task
- And/or bypass/disable caching so your code actually runs
Options:
- Raise instead of returning a State (recommended)
- Disable caching for this task
- Force a refresh to ignore cache on this run
- Change the cache key (e.g., add an input that varies)
Examples:
1) Raise to fail, and disable caching
from prefect import task, get_run_logger
from prefect.cache_policies import NO_CACHE
@task(cache_policy=NO_CACHE) # or use persist_result=False
def rsync_task(source, destination, credentials, subfolder, task_name):
logger = get_run_logger()
try:
cmd = (
f"rsync -avz {source} {credentials['user']}@{credentials['host']}:{destination} && "
f"rsync -rlvz {source} {credentials['user']}@{credentials['host']}:{destination}"
)
fabric_run(cmd)
<http://logger.info|logger.info>(f"Folder {subfolder} rsync completed successfully")
except Exception as e:
logger.error(f"Folder {subfolder} rsync failed: {e}")
raise # fail the task
2) If you really want to return a Failed state, be sure to bypass cache so the code runs:
from prefect import task
from prefect.cache_policies import NO_CACHE
from prefect import states
@task(cache_policy=NO_CACHE) # or @task(refresh_cache=True)
def my_task():
try:
...
except Exception as e:
return states.Failed(message=str(e))
Notes:
- Prefect only serves cached results from prior Completed runs. Failed states are not used as cache hits.
- In your snippet, return ...; raise means the raise is unreachable—pick one. If you want failure, use raise.
If helpful, here’s more background on results and caching in Prefect 3:
- Results and result storage: Results and caching (Prefect docs)
- Task states overview: States (Prefect docs)
If you can share your task decorator settings (persist_result, cache_policy, refresh_cache) or confirm you’re seeing “Completed (Cached)” in the UI, I can suggest the minimal change for your setup.