How It Works Features Pricing Blog Error Guides
Log In Start Free Trial
FastAPI · Python

Fix RuntimeError: RuntimeError: Task got bad yield: <coroutine object send_notification at 0x7f8b2c3a1d00> in FastAPI

This error occurs when a background task is defined as an async function but is not properly awaited, or when you pass a coroutine object instead of a callable to BackgroundTasks.add_task(). Fix it by passing the function reference and its arguments separately to add_task() instead of calling the async function directly.

Reading the Stack Trace

Traceback (most recent call last): File "/app/venv/lib/python3.11/site-packages/uvicorn/protocols/http/h11_impl.py", line 406, in run_asgi result = await app(scope, receive, send) File "/app/venv/lib/python3.11/site-packages/starlette/background.py", line 43, in __call__ await task() File "/app/venv/lib/python3.11/site-packages/starlette/background.py", line 26, in __call__ await self.func(*self.args, **self.kwargs) File "/app/src/tasks/notifications.py", line 12, in send_notification await email_client.send(to=email, subject=subject, body=body) RuntimeError: Task got bad yield: <coroutine object send_notification at 0x7f8b2c3a1d00>

Here's what each line means:

Common Causes

1. Passing a called coroutine instead of a function reference

The async function is called directly, producing a coroutine object, instead of passing the function reference for BackgroundTasks to call later.

@app.post("/orders")
async def create_order(
    order: OrderCreate,
    background_tasks: BackgroundTasks,
):
    new_order = save_order(order)
    background_tasks.add_task(send_notification(new_order.user_email, "Order placed"))
    return new_order

2. Mixing sync and async background tasks

A synchronous wrapper is passed to add_task that internally calls an async function without awaiting it.

def notify_user(email: str):
    send_notification(email, "Hello")  # Returns unawaited coroutine

background_tasks.add_task(notify_user, user.email)

3. Forgetting to pass arguments separately

Arguments are passed inside the function call instead of as separate parameters to add_task.

background_tasks.add_task(lambda: send_notification(email, subject))

The Fix

Pass the function reference (send_notification) and its arguments as separate parameters to add_task(). BackgroundTasks will call the function with the arguments after the response is sent. Do not call the async function yourself, as that creates a coroutine object instead of letting add_task manage execution.

Before (broken)
@app.post("/orders")
async def create_order(
    order: OrderCreate,
    background_tasks: BackgroundTasks,
):
    new_order = save_order(order)
    background_tasks.add_task(send_notification(new_order.user_email, "Order placed"))
    return new_order
After (fixed)
@app.post("/orders")
async def create_order(
    order: OrderCreate,
    background_tasks: BackgroundTasks,
):
    new_order = save_order(order)
    background_tasks.add_task(
        send_notification,
        new_order.user_email,
        "Order placed",
    )
    return new_order

Testing the Fix

import pytest
from unittest.mock import patch, AsyncMock
from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)


def test_create_order_returns_201():
    response = client.post(
        "/orders",
        json={"product_id": 1, "quantity": 2},
    )
    assert response.status_code in (200, 201)


@patch("app.tasks.notifications.send_notification", new_callable=AsyncMock)
def test_background_task_is_called(mock_send):
    response = client.post(
        "/orders",
        json={"product_id": 1, "quantity": 2},
    )
    assert response.status_code in (200, 201)
    mock_send.assert_called_once()


@patch("app.tasks.notifications.send_notification", new_callable=AsyncMock)
def test_background_task_receives_correct_args(mock_send):
    response = client.post(
        "/orders",
        json={"product_id": 1, "quantity": 2},
    )
    args = mock_send.call_args
    assert "Order placed" in str(args)

Run your tests:

pytest tests/test_background_tasks.py -v

Pushing Through CI/CD

git checkout -b fix/fastapi-background-task,git add src/routes/orders.py tests/test_background_tasks.py,git commit -m "fix: pass function reference to add_task instead of coroutine",git push origin fix/fastapi-background-task

Your CI config should look something like this:

name: CI
on:
  pull_request:
    branches: [main]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'
      - run: pip install -r requirements.txt
      - run: pytest --tb=short -q

The Full Manual Process: 18 Steps

Here's every step you just went through to fix this one bug:

  1. Notice the error alert or see it in your monitoring tool
  2. Open the error dashboard and read the stack trace
  3. Identify the file and line number from the stack trace
  4. Open your IDE and navigate to the file
  5. Read the surrounding code to understand context
  6. Reproduce the error locally
  7. Identify the root cause
  8. Write the fix
  9. Run the test suite locally
  10. Fix any failing tests
  11. Write new tests covering the edge case
  12. Run the full test suite again
  13. Create a new git branch
  14. Commit and push your changes
  15. Open a pull request
  16. Wait for code review
  17. Merge and deploy to production
  18. Monitor production to confirm the error is resolved

Total time: 30-60 minutes. For one bug.

Or Let bugstack Fix It in Under 2 minutes

Every step above? bugstack does it automatically.

Step 1: Install the SDK

pip install bugstack

Step 2: Initialize

import bugstack

bugstack.init(api_key=os.environ["BUGSTACK_API_KEY"])

Step 3: There is no step 3.

bugstack handles everything from here:

  1. Captures the stack trace and request context
  2. Pulls the relevant source files from your GitHub repo
  3. Analyzes the error and understands the code context
  4. Generates a minimal, verified fix
  5. Runs your existing test suite
  6. Pushes through your CI/CD pipeline
  7. Deploys to production (or opens a PR for review)

Time from error to fix deployed: Under 2 minutes.

Human involvement: zero.

Try bugstack Free →

No credit card. 5-minute setup. Cancel anytime.

Deploying the Fix (Manual Path)

  1. Run the test suite locally to confirm background tasks execute correctly.
  2. Open a pull request with the corrected add_task call.
  3. Wait for CI checks to pass on the PR.
  4. Have a teammate review and approve the PR.
  5. Merge to main and verify background task execution in staging logs.

Frequently Asked Questions

BugStack verifies that background tasks are properly queued and executed by mocking the task function, confirming it receives the correct arguments, and running your full test suite.

BugStack never pushes directly to production. Every fix goes through a pull request with full CI checks, so your team can review the background task changes before merging.

Yes. Background tasks run after the response is sent, so failures do not affect the client response. Add try/except blocks and logging inside your background tasks to catch and record errors.

BackgroundTasks is fine for lightweight operations like sending emails. For long-running or CPU-intensive tasks, use Celery or another task queue with a dedicated worker process.