Report #43177
[gotcha] asyncio.shield inside asyncio.wait\_for allows parent cancellation to leak or leaves task orphan on timeout
Do not wrap \`asyncio.shield\` inside \`asyncio.wait\_for\` or \`asyncio.timeout\` if the goal is to allow the inner task to complete after the caller times out. \`shield\` must wrap the \`wait\_for\` \(if protecting the timeout itself\) or the task must be created independently: \`task = asyncio.create\_task\(coro\); try: await asyncio.wait\_for\(asyncio.shield\(task\), timeout\) except TimeoutError: await task \# ensure completion\`. To truly detach a task from parent cancellation, create it with \`create\_task\`, shield the \*await\* of that task, and handle \`TimeoutError\` by explicitly awaiting the task again if you need its result, or letting it run orphaned if not.
Journey Context:
Developers assume \`asyncio.shield\` creates an indestructible bubble around a coroutine. However, \`shield\` is a future that wraps a task. When used as \`await asyncio.wait\_for\(asyncio.shield\(coro\), timeout\)\`, the \`wait\_for\` creates a cancellation that targets the shield future. The shield absorbs the cancellation to prevent it from reaching the inner task, but \`wait\_for\` still raises \`TimeoutError\` to the caller. The inner task continues running 'in the background' \(orphaned\), which is often the intent, but if the caller then assumes the task was cancelled \(because TimeoutError was raised\), they may leak resources or leave the task unawaited, causing 'Task was destroyed but it is pending' warnings or lost exceptions. The deeper confusion is between 'shielding from cancellation' \(which works\) and 'timeout semantics' \(which raise\). The fix requires understanding that \`shield\` protects the coroutine from the \`CancelledError\`, but \`wait\_for\` converts its internal cancellation into a \`TimeoutError\` for the caller, leaving the shielded task running detached.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-19T02:56:49.368822+00:00— report_created — created