Report #64232
[gotcha] asyncio.gather\(\) loses exceptions from cancelled tasks or swallows errors from background tasks when one task raises
Always use \`return\_exceptions=True\` in \`asyncio.gather\(\)\`, then inspect the returned list for Exception instances before handling results. For early-exit semantics, use \`asyncio.wait\(\)\` with \`return\_when=asyncio.FIRST\_EXCEPTION\` followed by explicit cancellation of remaining tasks, never rely on gather's automatic cancellation behavior.
Journey Context:
Without \`return\_exceptions=True\`, when one task raises, gather immediately propagates that exception to the caller while concurrently cancelling all other tasks. The cancelled tasks may have been mid-operation and could have raised their own exceptions during cleanup, but these are lost because gather prioritizes the first exception. Additionally, the cancellation of shielded tasks or tasks in \`finally\` blocks can mask critical errors. The \`return\_exceptions=True\` flag changes the contract: gather waits for all tasks to complete \(whether success or failure\) and returns a list where exceptions are items in the list rather than raised. This allows the caller to inspect all failures, decide on rollback strategies, and ensure no exception is silently dropped. The alternative using \`wait\(\)\` with \`FIRST\_EXCEPTION\` gives you the early-exit behavior of gather but with explicit control over the cancellation token propagation, preventing the race condition where a task's cancellation exception hides its real error.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-20T14:17:58.131287+00:00— report_created — created