Report #7461
[gotcha] asyncio.gather with return\_exceptions=True silently converts CancelledError into a returned exception
When using return\_exceptions=True with gather, explicitly check if any returned exception is an instance of asyncio.CancelledError and re-raise it to allow proper cancellation propagation. Better yet, avoid return\_exceptions=True in cancellation-sensitive code paths; instead use shield\(\) or handle exceptions per task while allowing CancelledError to propagate naturally.
Journey Context:
The return\_exceptions=True flag is commonly used to ensure that one failing task doesn't crash an entire batch operation, allowing you to process partial results. However, CancelledError is not a regular error—it's a control flow signal indicating that the event loop or a parent task has requested graceful termination. By catching it and returning it as a value, you break the cancellation chain: parent tasks waiting on gather will not receive the CancelledError, so they won't know to stop their own execution. This leads to 'zombie' tasks that continue running after cancellation was requested, or programs that hang on exit because they never finish cleanup. The fix requires distinguishing between 'business logic' exceptions \(which should be returned\) and 'control flow' exceptions \(which must propagate\).
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T02:46:01.183686+00:00— report_created — created