MarcoM
05/25/2026, 8:47 AMPausing flow, execution will continue when this flow run is resumed.
Concurrency lease renewal failed - slots are no longer reserved. Terminating execution to prevent over-allocation.
Resuming flow run execution!
The key error is: Concurrency lease renewal failed - slots are no longer reserved. Terminating execution to prevent over-allocation.
What do you think is causing this? My guess is that while the flows were paused for the human-in-the-loop step, their concurrency leases expired and couldn't be renewed — so on resume, Prefect terminated execution to avoid over-allocating the concurrency slots. Is that correct, and if so, how should I handle long human-in-the-loop pauses without losing the concurrency leases?
Thanks!Marvin
05/25/2026, 8:47 AMMarvin
05/25/2026, 8:49 AMConcurrency lease renewal failed - slots are no longer reserved. Terminating execution to prevent over-allocation.
So the message means: “this run no longer owns the concurrency slot it started with, so continuing would violate the concurrency limit.”
A few important nuances:
This is especially likely if the pause is long.
Prefect leases are time-based and must be renewed periodically. If a run is paused long enough, or if renewal fails due to connectivity / worker / API issues while paused, the lease can be lost.
Pause does not mean “hold the slot forever.”
For long human approval steps, that’s generally not the right mental model.
If this is deployment-level concurrency, the behavior is stricter.
Prefect flow engine maintains a deployment concurrency lease for the run, and if renewal fails, execution is terminated to avoid over-allocation.
Best practice for long human-in-the-loop waits
Don’t hold scarce concurrency across the pause if you can avoid it.
Instead, structure the flow like:
1. do the work that needs concurrency
2. release the constrained resource / exit concurrency context
3. pause for human input
4. on resume, re-acquire concurrency before continuing
Conceptually:
with concurrency("my-limit"):
do_pre_approval_work()
approval = pause_flow_run(...)
with concurrency("my-limit"):
do_post_approval_work(approval)
That pattern is the safest because you are not reserving capacity while waiting on a human.
If you must keep execution alive across a long pause, there are only imperfect options:
- increase lease duration where configurable
- tolerate lease-renewal failure in user-managed concurrency contexts with raise_on_lease_renewal_failure=False
But that second option has an important tradeoff: Prefect may continue even though the slot was lost, which can overrun the intended limit. So I would not recommend that for strict resource protection.
For your case — 30 runs on one ECS task, all paused and later resumed — I’d suspect one of these:
- the runs were holding concurrency while paused
- the lease TTL elapsed during the pause
- or the ECS task / connectivity prevented lease renewal while they were paused
So yes, your explanation is consistent with Prefect’s behavior.
Relevant docs:
- Global concurrency limits
- How to apply global concurrency and rate limits
- Interactive workflows / human-in-the-loop
If you want, I can help you determine which concurrency mechanism you’re hitting here:
- deployment concurrency
- global concurrency limit
- tag/task-run concurrency
- worker/work-pool level behavior
That distinction matters, because the mitigation is a bit different for each.MarcoM
05/25/2026, 8:59 AMConcurrency lease renewal failed - slots are no longer reserved error appeared only on some of the runs, not all 30. The ones that failed got terminated; the others resumed fine.
What I've already checked:
• The concurrency is NOT declared in the flow code — I'm not using with concurrency(...) anywhere.
• The work queue of the ECS work pool has "Flow Run Concurrency" set to Unlimited.
So the limit must come from somewhere else. My questions:
1. Given that the error hit only a subset of runs at resume, is this consistent with a concurrency limit (lower than 30) where the resumed runs competed for slots — the ones that got a slot continued, the ones that didn't got their lease revoked and terminated? Is my understanding correct?
2. Where should I look for the limit that's actually being hit? I still need to check: the work pool concurrency limit (separate from the queue), the deployment concurrency limit, and any global concurrency limits in the workspace. Are there other places a lease-based limit could come from?
3. For work-pool / deployment / global concurrency limits, the "release the slot before the pause, re-acquire on resume" pattern doesn't apply since those leases are managed by the engine for the whole run duration — correct? In that case, are my only real options to (a) raise the limit to at least 30, or (b) stagger the pauses/resumes so I never exceed the limit? Or is there a way to make a paused run not hold its lease and re-queue cleanly on resume instead of being terminated?Marvin
05/25/2026, 8:59 AMMarvin
05/25/2026, 9:02 AMwith concurrency(...), and the work queue flow-run concurrency is unlimited, the most likely source is deployment concurrency, not work-queue concurrency.
And for paused flows specifically:
Paused flow runs do not keep holding deployment concurrency forever.
When a run leaves Running and enters Paused, Prefect releases the deployment concurrency slot. On resume, it has to acquire one again before continuing.
So to your questions:
1. Is “some resumed, some failed” consistent with a lower concurrency limit than 30?
Yes, very much so.
That pattern is exactly what you’d expect if there is some engine-managed limit below 30 and all resumed runs tried to continue around the same time:
- all 30 paused
- their engine-managed slots were released while paused
- all 30 resumed together
- only some could reacquire slots
- the rest lost their lease / could not validate it and were terminated
So your competition model is directionally correct, with one refinement:
It’s not that they kept an old lease during pause and then fought to renew it on resume.
It’s more like:
- pause released the slot
- resume required reacquisition / validation
- some succeeded
- some didn’t, and the engine detected the lease was no longer valid
That matches the “subset failed” behavior much better than a pure lease-expiry-during-pause explanation.
2. Where should you look for the actual limit?
The main places to check are:
Deployment concurrency limit
This is the first place I’d look.
In Prefect 3, deployment concurrency uses leases and is tightly tied to flow-run orchestration. If only some resumed runs continued, this is the strongest candidate.
Work pool concurrency limit
Yes, check this too. This is separate from queue concurrency.
You already checked:
- work queue flow run concurrency = unlimited
But the work pool itself can still have its own concurrency cap.
Global concurrency limits
Check these as well, especially if you have any automation or workspace-level limit configured that applies indirectly to these runs.
That said, global concurrency limits are usually associated with explicit concurrency usage in code or deployment-integrated features, so if you truly have no concurrency declarations in flow code, deployment/work-pool are more likely than a random workspace global limit.
Other places worth checking
A few other things can look similar operationally, though they are less likely to produce this exact lease message:
- deployment-level concurrency options
- worker/work-pool provisioning capacity constraints
- automations that resume many runs at once, causing a burst against deployment/work-pool limits
But for lease-based limits specifically, the important ones are:
- deployment concurrency
- possibly work pool concurrency if enforced in your setup
- global concurrency if explicitly configured
Since the error explicitly mentions a lease, I would prioritize:
1. deployment
2. work pool
3. global concurrency limits
3. Does “release before pause, reacquire on resume” apply to engine-managed limits?
For deployment/work-pool/engine-managed orchestration concurrency, you don’t manually control that in user code — so yes, your intuition is right that the usual code pattern does not apply there.
But there’s one subtle correction:
Paused runs already do release the engine-managed deployment lease.
So there is not currently a separate user action you need to take to “release before pause.” Prefect is already doing that as part of pausing.
The real limitation is:
you do not have a supported knob to tell Prefect, “on resume, just requeue cleanly until a slot is available instead of terminating if the lease is gone / unavailable.”
So in practice, your options are mostly operational:
a) Raise the relevant limitMarvin
05/25/2026, 9:02 AM