thiago
12/01/2025, 1:34 PM2025-12-01 13:31:32,983 INFO prefect.flow_runs.runner [trace_id=0 span_id=0] ‣ Process for flow run 'conscious-pelican' exited cleanly.
2025-12-01 13:31:33,028 WARNING prefect.FlowRunCancellingObserver [trace_id=0 span_id=0] ‣ Consumer task exited with exception
Traceback (most recent call last):
File "/usr/local/lib/python3.12/site-packages/prefect/runner/_observers.py", line 176, in __aexit__
await self._consumer_task
File "/usr/local/lib/python3.12/site-packages/opentelemetry/instrumentation/asyncio/__init__.py", line 299, in trace_coroutine
return await coro
^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/prefect/runner/_observers.py", line 67, in _consume_events
async for event in self._events_subscriber:
File "/usr/local/lib/python3.12/site-packages/prefect/events/clients.py", line 642, in __anext__
message = orjson.loads(await self._websocket.recv())
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/websockets/asyncio/connection.py", line 322, in recv
raise self.protocol.close_exc from self.recv_exc
websockets.exceptions.ConnectionClosedError: no close frame received or sent
2025-12-01 13:31:33,034 ERROR asyncio [trace_id=0 span_id=0] ‣ Exception in callback FlowRunCancellingObserver._start_polling_task.<locals>.<lambda>(<Task cancell...nit__.py:276>>) at /usr/local/lib/python
handle: <Handle FlowRunCancellingObserver._start_polling_task.<locals>.<lambda>(<Task cancell...nit__.py:276>>) at /usr/local/lib/python3.12/site-packages/prefect/runner/_observers.py:99>
Traceback (most recent call last):
File "/usr/local/lib/python3.12/asyncio/events.py", line 88, in _run
self._context.run(self._callback, *self._args)
File "/usr/local/lib/python3.12/site-packages/prefect/runner/_observers.py", line 103, in <lambda>
if task.exception()
^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/opentelemetry/instrumentation/asyncio/__init__.py", line 299, in trace_coroutine
return await coro
^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/prefect/utilities/services.py", line 156, in critical_service_loop
await anyio.sleep(sleep)
File "/usr/local/lib/python3.12/site-packages/anyio/_core/_eventloop.py", line 87, in sleep
return await get_async_backend().sleep(delay)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py", line 2369, in sleep
await sleep(delay)
File "/usr/local/lib/python3.12/asyncio/tasks.py", line 665, in sleep
return await future
^^^^^^^^^^^^
asyncio.exceptions.CancelledError
the UI says the flow was completed successfully, though
the flow code is as simple as
@flow(name="Picanha-666")
def picanha_666() -> None:
"""
This is a flow that will be used to test the OpenTelemetry tracing.
"""
logger = get_run_logger()
<http://logger.info|logger.info>("Picanha 666 flow started")
time.sleep(10 * random.random())
<http://logger.info|logger.info>("Picanha 666 flow ended")Marvin
12/01/2025, 1:34 PMMarvin
12/01/2025, 1:37 PMwebsockets.exceptions.ConnectionClosedError: no close frame received or sent
- followed by an asyncio.exceptions.CancelledError during shutdown
- Prefect falls back to polling if a WebSocket fails during operation; during shutdown it just logs the warning and continues. Your flow was already completed successfully, which matches what you see in the UI.
Common causes in Kubernetes
- Ingress or load balancer idle timeouts closing the WS abruptly
- WebSocket upgrade not fully enabled on the ingress/proxy
- Connection drained when the pod/process exits
If you want to reduce or eliminate the warning
- You can safely ignore it. If you want to quiet the logs:
- In code:
import logging
logging.getLogger("prefect.FlowRunCancellingObserver").setLevel(logging.ERROR)
- Or raise overall logging level via environment variable: PREFECT_LOGGING_LEVEL=ERROR
- Ensure your ingress keeps WebSockets open long enough:
- For NGINX Ingress, annotations like:
<http://nginx.ingress.kubernetes.io/websocket-services|nginx.ingress.kubernetes.io/websocket-services>: prefect-api-service
<http://nginx.ingress.kubernetes.io/proxy-read-timeout|nginx.ingress.kubernetes.io/proxy-read-timeout>: "3600"
<http://nginx.ingress.kubernetes.io/proxy-send-timeout|nginx.ingress.kubernetes.io/proxy-send-timeout>: "3600"
<http://nginx.ingress.kubernetes.io/proxy-connect-timeout|nginx.ingress.kubernetes.io/proxy-connect-timeout>: "600"
<http://nginx.ingress.kubernetes.io/proxy-buffering|nginx.ingress.kubernetes.io/proxy-buffering>: "off"
<http://nginx.ingress.kubernetes.io/proxy-request-buffering|nginx.ingress.kubernetes.io/proxy-request-buffering>: "off"
- For AWS ALB/ELB, increase the idle timeout (e.g., 90s or more).
- This only impacts the event stream used for cancellation monitoring; the flow run execution and state reporting are unaffected.
Related reports
- WebSocket closed without close frame (worker startup / proxy): prefecthq/prefect#18749
- Long watch sessions eventually hit WS reset; run still fine: prefecthq/prefect#18428
Questions to help tailor guidance
- Are you on Prefect Cloud or self-hosted Prefect Server?
- Which ingress/controller are you using (nginx, ALB, Istio, etc.)?
- Do you see this only at the end of runs, or during long-running flows too?
Side note on your code: using time.sleep is fine for a simple demo like this. If you move to async tasks/concurrency later, prefer an async sleep (e.g., await anyio.sleep(...)) inside async tasks to avoid blocking the event loop.thiago
12/01/2025, 1:40 PMMarvin
12/01/2025, 1:40 PMMarvin
12/01/2025, 1:40 PM<http://nginx.ingress.kubernetes.io/proxy-read-timeout|nginx.ingress.kubernetes.io/proxy-read-timeout>: "3600"
<http://nginx.ingress.kubernetes.io/proxy-send-timeout|nginx.ingress.kubernetes.io/proxy-send-timeout>: "3600"
<http://nginx.ingress.kubernetes.io/proxy-connect-timeout|nginx.ingress.kubernetes.io/proxy-connect-timeout>: "600"
<http://nginx.ingress.kubernetes.io/proxy-buffering|nginx.ingress.kubernetes.io/proxy-buffering>: "off"
<http://nginx.ingress.kubernetes.io/proxy-request-buffering|nginx.ingress.kubernetes.io/proxy-request-buffering>: "off"
Notes
- You don’t need a special “websocket-services” annotation for the community NGINX Ingress; Upgrade headers are handled automatically.
- If you’re using kubectl port-forward instead of ingress, these warnings are common — port-forwarded WebSockets can drop unexpectedly. Prefer an ingress or NodePort for persistent WS connections.
You can safely ignore the warning
- It doesn’t affect run outcomes. Prefect will fall back to polling if the WebSocket actually fails during operation; during shutdown it just logs the warning.
- If you want to quiet it locally:
import logging
logging.getLogger("prefect.FlowRunCancellingObserver").setLevel(logging.ERROR)
or set PREFECT_LOGGING_LEVEL=ERROR.
Related reports for reference:
- prefecthq/prefect#18749
- prefecthq/prefect#18428
A couple quick questions so I can tailor the exact steps:
- Are you exposing Prefect Server via the microk8s NGINX Ingress, or using kubectl port-forward?
- Are you deploying with the Helm chart or your own manifests?
- Do you see this only at the end of runs, or also mid-run for long-running flows?