tests/gateway/test_kanban_wake_acceptance.py intermittently hung until the
CI runner SIGKILLed it at 600s (normally ~4s). The faulthandler stack of a
reproduced hang is the loop thread inside the test's own drain() helper:
while adapter._background_tasks:
await asyncio.gather(*list(adapter._background_tasks))
Since Python 3.12, asyncio.gather() completes eagerly when every child is
already done, so `await gather(...)` returns without yielding to the event
loop. When a session task finishes in the same loop iteration that resumes
the test (e.g. right after the notifier's advance() to_thread hop), the task
is done but its _background_tasks.discard done-callback is still queued for
the next iteration. drain() then re-checks a set that can never shrink and
spins forever without ever letting the loop run that callback.
Yield once after each gather so queued done-callbacks run before the set is
re-checked. Same fix for the identical helper in
tests/gateway/test_completion_admission.py and evals/heartbeat_idle_wire.py.
Production code is not affected (BasePlatformAdapter.cancel_background_tasks
filters done tasks and is bounded to 5 rounds).