Amith M
04/23/2026, 3:10 PMMarvin
04/23/2026, 3:10 PMMarvin
04/23/2026, 3:14 PM[Errno 98] error while attempting to bind on address ('127.0.0.1', 8781): address already in use
AssertionError: Reactive triggers not started
and then later:
File ".../prefect/deployments/runner.py", line 1224, in afrom_storage
await storage.pull_code()
...
RuntimeError: Failed to clone repository '<https://github.com/Armor1AI/armor1-apps.git>'
What’s happening
When Prefect creates deployments programmatically with flow.to_deployment() / flow.ato_deployment() and there is no explicit PREFECT_API_URL, Prefect can spin up a temporary local API server in-process / subprocess. That temporary server uses a localhost port in the 8000–9000 range. Your logs show one of those attempts trying 127.0.0.1:8781 and failing because it’s already in use.
Once that startup fails, shutdown can also error with:
AssertionError: Reactive triggers not started
So the “only 8 flows” symptom is likely a side effect of repeated deployment creation causing server/process/port contention in your Cloud Run Job container, not an actual supported limit of 8.
What I’d recommend first
1. Set PREFECT_API_URL explicitly to your Prefect Cloud workspace API URL before running deployment creation.
- This avoids ephemeral local server creation entirely.
- Prefect Cloud docs: Connect to Prefect Cloud
2. Upgrade to the latest Prefect 3.x
- There has been work/fixes around ephemeral server lifecycle/cleanup.
- Release notes: Prefect releases
3. Avoid creating many deployments via concurrent ato_deployment() calls
- Even if you tried sequential mode, if each call creates storage and client state independently inside the same job process, you can still run into cleanup timing issues.
- Better to batch deployment registration through a single API-backed process.
4. Avoid repeated Git clone per flow if all flows come from the same repo
- Your traceback shows failure inside afrom_storage() → pull_code() → _clone_repo().
- If every flow points at the same Git repo URL, repeated deployment creation may be repeatedly cloning/fetching the same repo under contention.
Most likely root cause chain
- flow.ato_deployment(...)
- Prefect needs a client / deployment metadata handling
- no PREFECT_API_URL available, so an ephemeral API server is started
- one of those startup attempts binds localhost port 8781
- bind fails with Errno 98
- server startup/shutdown leaves the “Reactive triggers not started” assertion
- the deployment call then also fails while resolving storage / cloning repo
Concrete fixes
If you are using Prefect Cloud, make sure the job has:
PREFECT_API_URL=<https://api.prefect.cloud/api/accounts/<account-id>/workspaces/<workspace-id>>
PREFECT_API_KEY=<your-api-key>
If you are self-hosting Prefect server, set:
PREFECT_API_URL=http://<your-prefect-server-host>:4200/api
Also, if you want to fail fast instead of silently starting an ephemeral API:
PREFECT_SERVER_ALLOW_EPHEMERAL_MODE=false
That helps confirm whether ephemeral mode is the culprit.
If you’re deploying from Python code
Try to avoid per-flow ato_deployment() calls that each resolve source storage from Git separately. If all flows live in one checked-out repo already inside the container, prefer loading them from the local filesystem instead of Git-based storage for deployment creation.
Also, if you can share code, I’d specifically look at whether you are doing something like:
await asyncio.gather(*(deploy_single_flow(info) for info in flows))
because that is especially likely to trigger this.
Why the fixed number looks like 8Marvin
04/23/2026, 3:14 PMRuntimeError: Failed to clone repository '<https://github.com/Armor1AI/armor1-apps.git>'
That may be secondary, but it’s still worth checking:
- GitHub auth / rate limiting
- concurrent clone attempts to same destination
- temp disk limits in Cloud Run Job
- whether the repo is already present locally and can be used directly instead of cloning for every flow
If you want, paste:
- your Prefect version
- whether PREFECT_API_URL is set
- the code around deploy_single_flow
- whether these flows all use the same GitHub source
and I can suggest a safer deployment pattern.Amith M
04/23/2026, 3:39 PMMarvin
04/23/2026, 3:39 PMMarvin
04/23/2026, 3:42 PMuvicorn.error and a localhost bind failure at the same time suggests one of these is happening:
1. some code path is still trying to start a temporary local Prefect server, or
2. a subprocess / context is not seeing the same PREFECT_API_URL you think it is, even though the main deployment path is.
So: yes, weird — but still possible.
Why both can be true at once
A deployment operation is not always one single API call. During deployment creation, Prefect may:
- load flow metadata
- resolve storage/source
- clone/pull code
- validate pool/infrastructure config
- create/update deployment in the API
So you can absolutely have a situation where:
- some steps hit the remote Prefect API successfully
- another step, possibly in a different settings context or subprocess, still falls back to ephemeral/local server behavior
That would explain why you do see changes reflected remotely and still get localhost uvicorn errors.
The most likely explanation
In Prefect, get_client() checks the current resolved settings, not just “is there an env var somewhere in the container.” If the active settings context for a particular call/subprocess resolves PREFECT_API_URL as empty/falsy, Prefect may attempt ephemeral mode.
That means all of these are possible:
- your top-level process has PREFECT_API_URL
- one subprocess does not
- one temporary settings context overrides it
- one code path is launched with different env inheritance
- some child process logs uvicorn.error even while the main process is talking to Prefect Cloud
That would match your symptoms exactly.
Important detail from your traceback
Your failure is happening here:
flow_obj.to_deployment(...)
...
RunnerDeployment.afrom_storage(...)
...
await storage.pull_code()
...
Failed to clone repository ...
That suggests this is not purely “deployment registration” logic; it’s also interacting with storage/source loading. If your flows are built from a Git source, that path may be creating additional subprocess/context behavior.
So there may actually be two overlapping problems:
1. unexpected local ephemeral server startup attempt
2. Git clone/storage contention/failure during afrom_storage()
What I would check next
Please add this right before the failing deployment call and log it from the same function/process that calls `to_deployment`:
import os
from prefect.settings import PREFECT_API_URL, PREFECT_SERVER_ALLOW_EPHEMERAL_MODE
print("ENV PREFECT_API_URL =", os.getenv("PREFECT_API_URL"))
print("RESOLVED PREFECT_API_URL =", PREFECT_API_URL.value())
print("EPHEMERAL MODE =", PREFECT_SERVER_ALLOW_EPHEMERAL_MODE.value())
If possible, also log this inside any subprocess entrypoint if you have one.
What I’m looking for:
- if os.getenv("PREFECT_API_URL") is set but PREFECT_API_URL.value() is empty, then there is a settings-context override
- if both are set, but Prefect still tries ephemeral, then we need to identify the exact subprocess/code path doing it
- if the child process has no env var, then it’s env inheritance
Very useful defensive setting
Try forcing this:
PREFECT_SERVER_ALLOW_EPHEMERAL_MODE=false
If the weird localhost server attempt is real, that should either:
- stop it entirely, or
- turn the behavior into a clearer hard failure telling us which path is missing API config
That’s a very good debugging move here.
Why I don’t think your observation disproves ephemeral mode
You said:
if that was truely the case, then i shouldnt see the deployments or its changes reflect in the remote prefect serverNot necessarily. A run can: - create/update deployments 1–8 remotely just fine
Marvin
04/23/2026, 3:42 PMafrom_storage() / pull_code() / clone
- uvicorn.error shows up
- this runs in GCP Cloud Run Job
I suspect one of:
- concurrent or repeated Git-based source resolution is spawning code paths with different settings
- a subprocess used during deployment creation is not inheriting PREFECT_API_URL
- a Prefect bug/regression in deployment/storage handling is causing an unintended ephemeral server startup despite remote API configuration
Practical next steps
1. Set and log:
PREFECT_API_URL
PREFECT_API_KEY
PREFECT_SERVER_ALLOW_EPHEMERAL_MODE=false
2. Log both:
os.getenv("PREFECT_API_URL")
PREFECT_API_URL.value()
3. Confirm whether all flows are coming from:
- flow.from_source(...) with the same Git repo, or
- locally imported Python modules already present in the container
4. If the repo is already in the container, avoid recloning from Git during deployment creation if possible.
5. Upgrade to the latest Prefect 3.x if you haven’t already:
Prefect releases
Docs / references
- Connect to Prefect Cloud
- Deployments
- Prefect server
- Prefect releases
If you want, paste:
- the code that constructs flow_obj
- the exact to_deployment(...) / deploy(...) call
- whether you use from_source("<https://github.com/...>")
- output of the three debug prints above
and I can help pinpoint whether this is settings leakage, subprocess env inheritance, or the Git storage path specifically.Nate
04/23/2026, 3:43 PMNate
04/23/2026, 4:51 PMNate
04/23/2026, 5:28 PMAmith M
04/23/2026, 5:37 PMAmith M
04/23/2026, 5:54 PMAmith M
04/23/2026, 5:55 PMNate
04/23/2026, 6:01 PMNate
04/23/2026, 6:01 PMuvicorn.error - [Errno 98] error while attempting to bind on address ('127.0.0.1', 8781): [errno 98] address already in use
Nate
04/23/2026, 6:01 PMNate
04/23/2026, 6:02 PMAmith M
04/23/2026, 6:09 PMNate
04/23/2026, 6:09 PMAmith M
04/23/2026, 6:09 PMAmith M
04/23/2026, 6:24 PMhuh. id guess there's some GCP resource exhaustion of some kind going on then@Nate does that mean each flow does not cleanup after itself? the number 8 seems to be too specific right, it clones 8 times, the 9th time there is no space?
Nate
04/23/2026, 6:48 PMNate
04/23/2026, 6:49 PMAmith M
04/23/2026, 7:05 PMi don't see any evidence of that, do you?no i did dig into the code, and the flows do cleanup, its inside the TemporaryDirectory context which auto clears the cloned repo, however i do suspect it might be some kind of bytecode cache of mmap references via sys.modules. i think the imported modules are not cleared even tho the folders are
Amith M
04/23/2026, 7:08 PMi'd like to see what's going on with that exit code 1for some other unrelated reason, i did observe that the reason of clone failure is masked by prefect, i might be wrong, but my understanding was that twas to prevent leaking the secrets if the actual git clone failure log is shown, which will include the credentials
Amith M
04/23/2026, 7:09 PMNate
04/23/2026, 7:09 PMNate
04/23/2026, 7:10 PMAmith M
04/24/2026, 7:35 AMAmith M
04/24/2026, 7:35 AMNate
04/24/2026, 4:29 PMGCP NAT was the bottleneck which blocked the concurrent requestsgotcha, i was wondering if something like that was going on
huh. id guess there's some GCP resource exhaustion of some kind going on thenglad you have something working! thanks for helping expose the module leak at least!
Amith M
04/24/2026, 6:27 PM