Introduction

In Python asyncio, calling an async function without await creates a coroutine object but never schedules it for execution. Python detects this and issues a RuntimeWarning: coroutine was never awaited. The intended async operation silently never runs, which is a common source of bugs in async codebases.

Symptoms

  • RuntimeWarning: coroutine 'fetch_data' was never awaited printed at program exit
  • Async operations appear to do nothing (no errors, no results)
  • Warning includes the location where the unawaited coroutine was created:
  • `
  • sys:1: RuntimeWarning: coroutine 'send_notification' was never awaited
  • RuntimeWarning: Enable tracemalloc to get the object allocation traceback
  • `
  • No exception is raised - the program continues normally

Common Causes

  • Forgetting await when calling an async function
  • Calling async function in a synchronous context without asyncio.create_task()
  • Using asyncio.ensure_future() incorrectly
  • Fire-and-forget pattern without proper task management
  • Mixing sync and async code paths

Step-by-Step Fix

  1. 1.Identify the unawaited coroutine:
  2. 2.```bash
  3. 3.# Enable tracemalloc for detailed traceback
  4. 4.python -W error::RuntimeWarning your_script.py
  5. 5.# Or enable at runtime:
  6. 6.python -c "
  7. 7.import warnings
  8. 8.warnings.filterwarnings('error', category=RuntimeWarning)
  9. 9.import asyncio
  10. 10.asyncio.run(main())
  11. 11."
  12. 12.`
  13. 13.Fix missing await:
  14. 14.```python
  15. 15.# WRONG - coroutine created but never executed
  16. 16.async def main():
  17. 17.result = fetch_data(url) # Missing await!
  18. 18.print(result) # Prints <coroutine object>, not data

# CORRECT - await the coroutine async def main(): result = await fetch_data(url) print(result) # Prints actual data ```

  1. 1.Run async function from sync context properly:
  2. 2.```python
  3. 3.import asyncio

# WRONG - just creates coroutine object def sync_function(): fetch_data(url) # Does nothing

# CORRECT - run the async function def sync_function(): asyncio.run(fetch_data(url))

# Or in existing event loop context def sync_function(): loop = asyncio.get_event_loop() loop.run_until_complete(fetch_data(url)) ```

  1. 1.Fire-and-forget pattern with task management:
  2. 2.```python
  3. 3.import asyncio

background_tasks = set()

async def fire_and_forget(coro): """Schedule coroutine without awaiting it.""" task = asyncio.create_task(coro) background_tasks.add(task) task.add_done_callback(background_tasks.discard) return task

async def main(): # Send notification without blocking await fire_and_forget(send_notification(user_id, message)) # Continue with other work immediately

# Prevents "coroutine never awaited" warning # Task is tracked and cleaned up automatically ```

  1. 1.Use pytest-asyncio to catch unawaited coroutines in tests:
  2. 2.```python
  3. 3.import pytest

@pytest.mark.asyncio async def test_fetch_data(): # Test will fail if coroutine is not properly awaited result = await fetch_data("http://example.com") assert result is not None ```

Prevention

  • Enable PYTHONASYNCIODEBUG=1 during development for stricter checks
  • Use ruff or flake8-async linters to detect missing awaits
  • Adopt the pattern: all async functions should be called with await or create_task()
  • Use asyncio.gather() for running multiple coroutines concurrently
  • Never call async functions from synchronous code without asyncio.run() or event loop integration
  • Add -W error::RuntimeWarning to your test runner to fail on unawaited coroutines