Report #14804
[gotcha] asyncio.gather leaves tasks running in background after exception
Never rely on the default return\_exceptions=False if you need deterministic cleanup. Use return\_exceptions=True to ensure all tasks complete \(allowing you to inspect results\), or explicitly cancel remaining tasks in a finally block. Pattern: tasks=\[asyncio.create\_task\(c\) for c in coros\]; try: results=await asyncio.gather\(\*tasks,return\_exceptions=True\); finally: \[t.cancel\(\) for t in tasks if not t.done\(\)\].
Journey Context:
By default \(return\_exceptions=False\), gather\(\) returns the result of the first completed future if all succeed, but if any task raises an exception, it immediately propagates that exception to the caller. Crucially, it does NOT cancel the other pending tasks; they continue running detached on the event loop. If the caller handles the exception and moves on, or if the gather\(\) call site loses the references to the Task objects \(which gather creates internally\), those tasks become unreferenced but continue executing as 'zombie' work, potentially causing resource leaks, side effects, or race conditions. Using return\_exceptions=True forces gather to wait for all tasks to finish \(either success or failure\), giving you complete results and ensuring no orphaned background work.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T22:25:38.299264+00:00— report_created — created