Jon Young
06/22/2026, 2:27 PM"prefect==3.4.7" and prefecthq/prefect-aws:0.7.7-python3.12-prefect3.6.29 for our ecs worker. there are two issues with prefect ecs we are unsure how to resolve:
1. flow runs that are manually cancelled get stuck in a cancelling state
2. the concurrency limit for a given deployment's state gets out of sync. it claims that slots are taken despite no flow runs attached
both our are big deal to us, especially 2, because our throughput takes a hit. what should we do to resolve this?Marvin
06/22/2026, 2:27 PMMarvin
06/22/2026, 2:29 PMprefect==3.4.7
- ECS worker image prefecthq/prefect-aws:0.7.7-python3.12-prefect3.6.29
That version mix is old enough that both symptoms line up with known fixes made later in Prefect 3.x.
What’s likely happening
1. Flow runs stuck in Cancelling
- Older Prefect versions could leave runs in CANCELLING if the worker/infrastructure teardown path did not complete cleanly.
- A cleanup mechanism for stuck cancelling runs was added later in Prefect.
- Relevant release note: Prefect 3.7.5 release notes
- Specifically mentions: Add cancelling timeout cleanup producer in #22238
2. Deployment concurrency slots getting “stuck”
- There were fixes for deployment concurrency lease handling in later 3.x releases.
- This is consistent with a known class of bugs where the lease/slot was acquired but not reliably released during state transitions or teardown.
- One relevant fix was “Preserve deployment concurrency lease ID across state transitions” in #21280
3. ECS-specific worker/event handling
- There was also a later prefect-aws ECS fix around observer ack timing, which could contribute to state drift / missed cleanup behavior:
- #21927
What I would do
1. Upgrade to a newer, aligned version pair
Don’t keep running:
- Python package: prefect==3.4.7
- Worker image built for: prefect3.6.29
That version skew is not ideal by itself.
I’d move to a matched and current-enough pair, ideally at least:
- prefect >= 3.7.5
- a matching prefect-aws ECS worker image from the same era or newer
For example, use a worker image tagged with the same Prefect core version you install in your environment.
You can review current releases here:
- Core Prefect releases: Prefect releases
- Prefect AWS releases: prefect-aws releases
2. Make sure your worker image and control-plane package are aligned
Right now you have:
- app/runtime package: 3.4.7
- worker image runtime: 3.6.29
Even if this is not the sole cause, it increases the chance of weird orchestration/state behavior. I would standardize on one version.
3. After upgrade, retest both behaviors specifically
Create two simple tests:
- a long-running flow that you manually cancel
- a deployment with a low concurrency limit, then run/cancel/complete repeatedly and verify slots are released
4. If you cannot upgrade immediately, use operational mitigations
Short term, you may need manual cleanup for both symptoms.
For stuck CANCELLING runs, you can force a terminal state via the API:
python
from prefect.client.orchestration import get_client
from prefect.states import Cancelled
import asyncio
async def force_cancel(flow_run_id: str):
async with get_client() as client:
await client.set_flow_run_state(
flow_run_id=flow_run_id,
state=Cancelled(message="Manual recovery from stuck CANCELLING state"),
)
asyncio.run(force_cancel("YOUR_FLOW_RUN_ID"))
I have not verified `set_flow_run_state`’s exact client signature in your installed version, so if you want, I can help you with the exact snippet for 3.4.7.
5. Investigate whether the slots are truly orphaned vs hidden by filters
Before assuming only UI drift, confirm via API/DB whether:
- the deployment/global concurrency limit shows occupied slots
- there are no corresponding active flow runs in RUNNING, PENDING, SCHEDULED, CANCELLING, etc.
If you’re self-hosting the API/database, DB inspection may help. If you’re on Cloud, use the API/UI.
Bottom lineMarvin
06/22/2026, 2:29 PMPrefect 3.7.5
- upgrade prefect-aws to a matching newer release
- ensure the ECS worker image version matches the prefect version used by your deployments/workers
Why I’m confident that’s the right direction
Your two symptoms map closely to known fixes:
- stuck cancellations cleanup: #22238
- deployment concurrency lease preservation: #21280
- ECS observer ack fix: #21927
If you want, I can next help you with one of these:
1. a safe upgrade target/version matrix for prefect + prefect-aws
2. a runbook to clean up stuck concurrency slots today
3. a minimal repro/checklist to verify whether ECS cancellation is failing at the worker, task, or orchestration layerJon Young
06/22/2026, 7:23 PMJon Young
06/22/2026, 7:23 PMMarvin
06/22/2026, 7:23 PMMarvin
06/22/2026, 7:24 PMprefect==3.4.7
- your ECS worker image is prefecthq/prefect-aws:0.7.7-python3.12-prefect3.6.29
So your worker is running Prefect 3.6.29, while you say “we’re on” 3.4.7. That means at least part of your system is not on the same core version.
Why that matters
Prefect orchestration, worker behavior, cancellation handling, and concurrency lease cleanup all depend on tightly coordinated state transitions. When you combine:
- older orchestration behavior
- ECS-specific worker code
- version skew between worker/runtime pieces
…you increase the chance of exactly the kind of drift you’re seeing:
- runs stuck in CANCELLING
- concurrency slots that never get released
More concretely
There are later fixes for:
- stuck CANCELLING cleanup: #22238
- concurrency lease preservation: #21280
- ECS observer ack behavior: #21927
So “what’s up with that?” = you’re very likely running into already-fixed bugs, made harder to reason about by version mismatch.
One more important nuance
I don’t want to overstate “mismatch” as the sole cause without confirming your architecture. There are a few possibilities:
- Case 1: self-hosted server/API is 3.4.7, ECS worker image is 3.6.29
- this is definitely skew, and I’d fix it first
- Case 2: your flow code environment is 3.4.7, but workers are 3.6.29, and server is something else
- still skew, still suspicious
- Case 3: you’re on Prefect Cloud, but your runtime/worker pieces are split between 3.4.7 and 3.6.29
- less about server mismatch, more about worker/runtime behavior still being inconsistent and outdated
Best next step
Can you clarify which of these 3.4.7 refers to?
1. your application environment running flow code
2. your self-hosted Prefect server/API
3. both
And are you on:
- Prefect Cloud, or
- self-hosted Prefect server?
That will let me tell you whether this is primarily:
- a worker bug
- an API/server orchestration bug
- or a cross-version compatibility problem.Jon Young
06/22/2026, 7:29 PM3.6.29
image defines: prefecthq/prefect-aws:0.7.9-python3.12-prefect3.7.5Jon Young
06/22/2026, 7:29 PMMarvin
06/22/2026, 7:29 PMMarvin
06/22/2026, 7:29 PMprefect==3.4.7 refers to:
- flow code environment
- server/API
- both
If you want, paste:
bash
prefect version
and, if self-hosted, the server version too. That’ll let me give you a precise answer instead of guessing.Jon Young
06/22/2026, 7:29 PM3.6.29
image defines: prefecthq/prefect-aws:0.7.9-python3.12-prefect3.7.5Marvin
06/22/2026, 7:30 PMMarvin
06/22/2026, 7:30 PM3.6.29
- but your image tag says prefecthq/prefect-aws:0.7.9-python3.12-prefect3.7.5
then the main thing “up with that” is:
your running worker does not appear to be using the image/version you think it is.
That is the first thing I would investigate.
Why this matters
You previously mentioned:
- prefect==3.4.7
- worker image around 0.7.7 ... prefect3.6.29
Now you’re saying:
- Cloud
- worker runtime says 3.6.29
- configured image says 0.7.9 ... prefect3.7.5
Those two facts are inconsistent. If the image really is ...prefect3.7.5, I would expect prefect version inside that running worker container to report 3.7.5, not 3.6.29.
So likely one of these is true:
1. old ECS task definition / old image revision is still being used
2. worker service was not redeployed after changing the image
3. there are multiple workers and the one picking up work is still old
4. the container is overriding/installing a different prefect version at startup
5. you’re checking version in a different container than the one actually running the worker
What I’d do first
1. Verify the actual running image digest/tag in ECS
In AWS ECS, check the running task for the worker service and confirm:
- task definition revision
- container image URI/tag
- image digest actually pulled
Do not rely only on what is “configured in code”; verify the running task.
2. Exec into the running worker container and check:
bash
prefect version
python -c "import prefect; print(prefect.__version__)"
pip show prefect
pip show prefect-aws
If those still say 3.6.29, then the 3.7.5 image is not actually what’s running, or something is downgrading Prefect in-container.
3. Check for startup-time installs
Look for any of these in your Dockerfile / entrypoint / ECS command:
- pip install prefect==...
- uv pip install ...
- poetry install
- app startup scripts that reinstall dependencies
A common pattern is:
- base image has Prefect 3.7.5
- app layer installs prefect==3.6.29 from requirements.txt
- final running container reports 3.6.29
4. Check whether multiple workers are registered
If one old ECS worker is still alive, it may be the one processing cancellations and acquiring concurrency slots.
You can inspect workers in Cloud UI and verify:
- worker names
- last polled time
- work pool association
- whether old workers are still online
Why this probably explains your symptoms
If the live worker is really still 3.6.29, then:
- you may not actually have the ECS observer fix you thought you deployed
- you may not have the cancellation improvements you thought you deployed
- you may still be seeing the older concurrency behavior
So before going deeper into Prefect behavior, I would resolve the version discrepancy.
What I think is most likely
Most likely:
- the ECS worker service is still running an old task revision or image
- or your app layer/install step overwrote the Prefect version inside the image
Fastest way to prove it
From inside the running worker container, gather:
bash
prefect version
python -c "import prefect, prefect_aws; print(prefect.__version__); print(prefect_aws.__version__)"
pip freeze | grep prefect
If you paste that output, I can tell you immediately whether:
- the wrong image is running
- the image is being mutated at runtime
- or there’s something else unusual going on
Bottom line
Right now I would not assume the 0.7.9 / prefect3.7.5 image is actually deployed. The mismatch between:
- reported worker version 3.6.29
- expected image version 3.7.5
is the biggest red flag in the whole thread.Jon Young
06/22/2026, 7:35 PMJon Young
06/22/2026, 7:50 PMMarvin
06/22/2026, 7:50 PMMarvin
06/22/2026, 7:53 PMCANCELLING
3. A worker notices that state change
4. The worker tries to stop the backing infrastructure
- for ECS, that means calling ECS to stop the task
5. Once cleanup succeeds, the flow run moves to CANCELLED
So CANCELLING is an in-between state, not the final one.
Important nuance for ECS
The cancellation is tied to:
- a worker being alive and observing the run, and
- the run having a valid infrastructure identifier for the ECS task
In practice, the worker needs to know: “this flow run corresponds to ECS task X in cluster Y”, then it can stop that ECS task.
What it is not tied to
It is not about whichever Prefect task function happens to be currently executing inside your flow. Prefect isn’t interrupting a specific Python task object directly. Instead, it cancels by stopping the execution environment backing the flow run.
For ECS that usually means:
- stopping the whole ECS task/container running the flow run
Why runs can get stuck in CANCELLING
That happens if:
- the worker never sees/processes the cancellation
- the worker crashes
- the ECS task can’t be stopped cleanly
- the infrastructure PID/task identifier is missing or wrong
- the worker version has a bug in cancellation handling
That’s why your version mismatch matters so much:
- if the worker that is actually live is older than you expect, it may be missing fixes in this path
One more nuance
There is also a newer cleanup mechanism in later Prefect versions so runs don’t remain in CANCELLING forever:
- #22238
So in newer versions:
- ideal path: worker sees cancel, stops ECS task, marks CANCELLED
- fallback path: cleanup service eventually forces the run to CANCELLED
For your exact question: “is it tied to whatever task is live?”
Best concise answer:
- No, not to an individual Prefect task run
- Yes, to the live worker + live ECS infrastructure record for that flow run
If you want, I can next explain:
1. how deployment concurrency slots are supposed to be released during cancellation, or
2. exactly what to inspect in Cloud/ECS when a run stays in CANCELLINGJon Young
06/22/2026, 8:01 PM3.7.5.
1. the UI shows that the flow run is cancelled
2. the ecs task stopped
3. BUT, there is still an active task in the flow run, the one that was live when the flow run was cancelled
3 seems like a bug?Marvin
06/22/2026, 8:01 PMMarvin
06/22/2026, 8:05 PMCANCELLED
2. ECS task is stopped
3. task runs that were in-flight do not get updated instantly
4. a background cleanup process later marks those child task runs terminal
So a brief period where:
- flow run = CANCELLED
- one task run still looks active
can be expected.
What is not expected
If that task remains active for a while — especially minutes, not seconds — then yes, I would treat that as a bug / cleanup failure.
From the code path, child task-run cleanup is handled asynchronously by server-side cancellation cleanup logic, not inline with the flow run transition. So there is a race/window.
Rule of thumb
- A few seconds / short delay: probably expected
- Persistent active task after the flow is cancelled and ECS task is gone: not good, likely a bug
Why this happens conceptually
When the ECS task is killed, the Python process running the task disappears immediately. That means the task never gets a chance to report:
- Cancelled
- Failed
- Crashed
So Prefect has to reconcile that afterward from the outside.
If the reconciliation succeeds, the task run should eventually be marked terminal.
If it doesn’t, you’re left with an orphaned “active” task run.
What I’d check next
Please confirm:
- how long the task stays active after the flow run is shown as CANCELLED
- whether it is shown as RUNNING, PENDING, or something else
- whether this stale task run is also what’s causing your concurrency slot issue
Those details matter.
Most likely answer to your question
“3 seems like a bug?”
- Yes, if it persists
- Not necessarily, if it’s only transient
What you can do right now
If you’re self-hosting, I’d suggest checking cleanup service settings — but since you’re on Prefect Cloud, that part is managed.
So for Cloud, if you can reproduce this reliably on 3.7.5 with:
- flow run cancelled
- ECS task stopped
- child task run remains active for a long time
then that’s strong evidence of a product bug.
Useful thing to capture for a repro
If you want, I can help you write a minimal repro flow, but the ideal data to capture is:
- flow run ID
- task run ID
- timestamps for:
- task start
- cancel click
- flow run became CANCELLED
- ECS task stopped
- task run still active at T+N minutes
Since I can’t file a ticket for you, the best public route if you want Prefect engineering eyes is:
- GitHub issues: prefect issues
- or contact Prefect: prefect contact
If you want, I can help you turn this into a tight minimal repro + issue draft so you can post it cleanly.Jon Young
06/22/2026, 8:06 PMMarvin
06/22/2026, 8:06 PMMarvin
06/22/2026, 8:06 PMtext
Title:
Cancelled flow run on ECS leaves in-flight task run active after ECS task has stopped
Body:
## Summary
On Prefect Cloud with ECS workers, cancelling a flow run can result in this sequence:
1. Flow run transitions to `CANCELLED`
2. ECS task is stopped
3. The task run that was active at cancellation time remains active/non-terminal in the UI/API
There may be a brief reconciliation window where this is expected, but in our case the task run appears to remain active after the flow run is already `CANCELLED` and the ECS task is gone.
## Environment
- Prefect Cloud
- Worker image: `prefecthq/prefect-aws:0.7.9-python3.12-prefect3.7.5`
- Worker/runtime Prefect version: `3.7.5`
- ECS worker
## Observed behavior
After manually cancelling a flow run:
- the flow run is shown as `CANCELLED`
- the ECS task stops successfully
- the in-flight task run remains active/non-terminal instead of being reconciled to a terminal state
## Expected behavior
Once the flow run is `CANCELLED` and the backing ECS task has stopped, child task runs that were in-flight should be reconciled to a terminal state within a short period of time. They should not remain active indefinitely.
## Questions
- Is this expected to be eventually consistent only, or is this a bug?
- If expected to reconcile asynchronously, what is the expected upper bound for reconciliation?
- Could this leave deployment/task concurrency accounting out of sync?
## Reproduction sketch
A minimal repro seems to be:
1. Deploy a flow to ECS
2. Start a flow run with a long-running task
3. While the task is running, manually cancel the flow run
4. Observe:
- flow run becomes `CANCELLED`
- ECS task stops
- task run that was active at time of cancellation remains active/non-terminal
## Example flow
```python
from prefect import flow, task
import time
@task
def long_task():
time.sleep(600)
@flow
def cancel_test():
long_task()
if __name__ == "__main__":
cancel_test()
## Additional details to include
Please let me know if it would help to provide:
- flow run ID
- task run ID
- timestamps for cancellation and ECS task stop
- screenshots/API responses showing the task run still active
```
What I recommend adding before submitting
If you have them, add:
- how long the task stayed active after cancellation
- the exact task-run state shown in UI/API (RUNNING, PENDING, etc.)
- whether this also correlates with stuck concurrency slots
If you want, I can also make this shorter and more engineering-focused before you submit it.Bring your towel and join one of the fastest growing data communities. Welcome to our second-generation open source orchestration platform, a completely rethought approach to dataflow automation.
Powered by