This specification is maintained by the AgentRules Working Group. Have enhancements or want to claim maintainership?Submit Specification →
backendcritical PriorityID: fastapi-lifespan-taskgroup-drain-invariant

FastAPI Lifespan TaskGroup ExceptionGroup Drain Invariant

Guarantees that background workers, message consumers, and persistent coroutines managed within FastAPI lifespan contexts utilize Python 3.12+ asyncio.TaskGroup with graceful cancellation and comprehensive ExceptionGroup unwinding. Prevents silent coroutine abandonment, zombie connection leaks, and SIGTERM worker deadlocks during graceful shutdown. Enforces clean context drain before the ASGI application server finalizes process termination.

Prohibited Anti-Pattern: Fire-and-Forget asyncio.create_task Inside FastAPI Lifespan Without TaskGroup Drain or ExceptionGroup Handlers

Export Ready-to-Drop Configuration

Select format below to copy code or download file directly.

---
description: FastAPI Lifespan TaskGroup ExceptionGroup Drain Invariant - Guarantees that background workers, message consumers, and persistent coroutines managed within FastAPI lifespan contexts utilize Python 3.12+ asyncio.TaskGroup with graceful cancellation and comprehensive ExceptionGroup unwinding. Prevents silent coroutine abandonment, zombie connection leaks, and SIGTERM worker deadlocks during graceful shutdown. Enforces clean context drain before the ASGI application server finalizes process termination.
globs: **/*.py
alwaysApply: true
verified_by: "agentrules.org"
license: "CC-BY-4.0"
source: "https://agentrules.org/rules/fastapi-lifespan-taskgroup-drain-invariant"
---

# FastAPI Lifespan TaskGroup ExceptionGroup Drain Invariant

> **Severity**: CRITICAL | **Category**: backend
> **Anti-Pattern Warning**: Fire-and-Forget asyncio.create_task Inside FastAPI Lifespan Without TaskGroup Drain or ExceptionGroup Handlers

## System Constraints
You are a Principal Software Architect enforcing production-grade concurrency invariants in FastAPI applications running Python 3.12+. You strictly prohibit fire-and-forget background tasks launched via asyncio.create_task within application lifespan hooks without deterministic cancellation and drain protocols. Mandate the use of structured concurrency via asyncio.TaskGroup, deterministic SIGTERM/SIGINT teardown via asyncio.Event flags, and rigorous ExceptionGroup (PEP 654) unwinding using 'except*' syntax to guarantee all background tasks terminate cleanly before ASGI worker teardown.

## Strict Guidelines
- Engineers MUST ALWAYS encapsulate concurrent background tasks managed during the application lifespan within an explicit asyncio.TaskGroup or an equivalent structured concurrency supervisor.
- Engineers MUST ALWAYS signal background workers to stop using a coordinated asyncio.Event or threading.Event prior to exiting the lifespan context manager.
- Engineers MUST ALWAYS handle task failures during shutdown using Python 3.12+ 'except* ExceptionGroup' syntax to inspect and log individual sub-exceptions without masking termination signals.
- Engineers MUST NOT invoke raw 'asyncio.create_task()' inside the lifespan generator without retaining strong task references and explicit awaitable cancellation barriers in the post-yield cleanup phase.
- Services MUST enforce a deterministic timeout via 'asyncio.timeout()' around the lifespan task drain to prevent hung external I/O operations from blocking ASGI server worker cycling.

## Bad vs Good Implementations
### FastAPI Lifespan Background Task Drain with TaskGroup and except*

#### Bad Antipattern (AI Common Hallucination)
> **Why it fails**: Launching background tasks using untracked asyncio.create_task() violates structured concurrency. The task reference can be prematurely garbage collected, unhandled task exceptions escape unmonitored until loop garbage collection, and ASGI server shutdown (e.g., Uvicorn/Gunicorn SIGTERM) will terminate the process before the task can finalize inflight I/O or flush database buffers.
```python
import asyncio
from contextlib import asynccontextmanager
from fastapi import FastAPI

async def event_consumer():
    while True:
        await asyncio.sleep(1)
        # If this raises an unhandled error, it is silenced or crashes the loop arbitrarily

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Anti-pattern: Detached coroutine, no reference retention, no structured drain
    asyncio.create_task(event_consumer())
    yield
    # Teardown exits immediately while event_consumer is abruptly killed or left dangling

app = FastAPI(lifespan=lifespan)
```

#### Production Standard (Correct)
> **Standard**: Leverages Python 3.12+ asyncio.TaskGroup and except* syntax. Background tasks are coordinated via a shared asyncio.Event shutdown flag, guaranteed a bounded drain window via asyncio.timeout(), and cleanly collected upon context exit. Any compound failures are surfaced as typed ExceptionGroups, preventing zombie tasks and ensuring deterministic ASGI server process exits.
```python
import asyncio
import logging
from contextlib import asynccontextmanager
from fastapi import FastAPI

logger = logging.getLogger(__name__)

async def event_consumer(shutdown_event: asyncio.Event) -> None:
    try:
        while not shutdown_event.is_set():
            await asyncio.sleep(0.5)
    except asyncio.CancelledError:
        logger.info("Consumer received cancellation, flushing local buffers.")
        raise

@asynccontextmanager
async def lifespan(app: FastAPI):
    shutdown_event = asyncio.Event()
    # Launch supervisor in a controlled TaskGroup
    try:
        async with asyncio.TaskGroup() as tg:
            consumer_task = tg.create_task(event_consumer(shutdown_event))
            yield
            # Teardown sequence: signal stop and cancel if unresponsive
            shutdown_event.set()
            try:
                async with asyncio.timeout(5.0):
                    await asyncio.sleep(0)
            except* TimeoutError:
                logger.warning("Drain timed out; cancelling consumer task.")
                consumer_task.cancel()
    except* asyncio.CancelledError:
        logger.info("All lifespan tasks cancelled cleanly.")
    except* Exception as eg:
        logger.error("Unhandled exception(s) during lifespan execution: %s", eg.exceptions)
        raise

app = FastAPI(lifespan=lifespan)
```

Architectural Specification & Invariant Rationale

Architectural Invariant Design: FastAPI Lifespan TaskGroup ExceptionGroup Drain Invariant

FastAPI Lifespan TaskGroup ExceptionGroup Drain Invariant defines an industrial-grade behavioral standard for backend engineering. Guarantees that background workers, message consumers, and persistent coroutines managed within FastAPI lifespan contexts utilize Python 3.12+ asyncio.TaskGroup with graceful cancellation and comprehensive ExceptionGroup unwinding. Prevents silent coroutine abandonment, zombie connection leaks, and SIGTERM worker deadlocks during graceful shutdown. Enforces clean context drain before the ASGI application server finalizes process termination. Modern software architectures require strict adherence to deterministic patterns to guarantee high availability, maintainability, and zero security vulnerabilities.

Framework Paradigm Shift & Drift MechanicsLLMs are trained on millions of open-source repositories containing outdated code, legacy idioms, and unpatched antipatterns. When coding assistants generate implementations for this stack, they default to common statistical tokens: Fire-and-Forget asyncio.create_task Inside FastAPI Lifespan Without TaskGroup Drain or ExceptionGroup Handlers Without an explicit system instruction file, developers spend hours debugging silent failures.
Failure Modes & Production Blast Radius

Hazard Analysis: What Breaks Without This Rule

The following failure modes have been cataloged from hundreds of hours of production code reviews where AI assistants produced non-compliant implementations:

Failure ModeAI Symptom (The Anti-Pattern)SeverityProduction Impact & Remedy
AI Hallucination & Deprecation DriftFire-and-Forget asyncio.create_task Inside FastAPI Lifespan Without TaskGroup Drain or ExceptionGroup HandlersCritical
Violating this industrial standard introduces regression bugs, security risks, and unexpected runtime failures in production.
Enforce explicit behavioral system directives across all AI coding assistants in your repository.
Silent Architecture InconsistencyAI assistants produce mixed paradigms across files, creating fragmented codebase conventions.Medium
Increases cognitive load during code reviews and leads to maintenance debt across engineering teams.
Pin project conventions in .cursor/rules, CLAUDE.md, and AGENTS.md so all team agents follow identical guardrails.
Architectural Directives in Detail

Step-by-Step Production Directives

Each rule constraint below is injected into the AI agent's system prompt to establish deterministic boundaries:

1

Engineers MUST ALWAYS encapsulate concurrent background tasks managed during the application lifespan within an explicit asyncio.TaskGroup or an equivalent structured concurrency supervisor.

Engineers MUST ALWAYS encapsulate concurrent background tasks managed during the application lifespan within an explicit asyncio.TaskGroup or an equivalent structured concurrency supervisor.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
2

Engineers MUST ALWAYS signal background workers to stop using a coordinated asyncio.Event or threading.Event prior to exiting the lifespan context manager.

Engineers MUST ALWAYS signal background workers to stop using a coordinated asyncio.Event or threading.Event prior to exiting the lifespan context manager.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
3

Engineers MUST ALWAYS handle task failures during shutdown using Python 3.12+ 'except* ExceptionGroup' syntax to inspect and log individual sub-exceptions without masking termination signals.

Engineers MUST ALWAYS handle task failures during shutdown using Python 3.12+ 'except* ExceptionGroup' syntax to inspect and log individual sub-exceptions without masking termination signals.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
4

Engineers MUST NOT invoke raw 'asyncio.create_task()' inside the lifespan generator without retaining strong task references and explicit awaitable cancellation barriers in the post-yield cleanup phase.

Engineers MUST NOT invoke raw 'asyncio.create_task()' inside the lifespan generator without retaining strong task references and explicit awaitable cancellation barriers in the post-yield cleanup phase.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
5

Services MUST enforce a deterministic timeout via 'asyncio.timeout()' around the lifespan task drain to prevent hung external I/O operations from blocking ASGI server worker cycling.

Services MUST enforce a deterministic timeout via 'asyncio.timeout()' around the lifespan task drain to prevent hung external I/O operations from blocking ASGI server worker cycling.

Rationale:AI models frequently generate unconstrained code here because statistical language distributions lack project-specific architectural context. Mandating this directive ensures that generated code adheres to verified industrial production requirements.
Enforcement:Integrated into the Agent Pack rule file. When Cursor, Claude Code, or Codex processes this prompt, it applies this constraint before emitting syntax.
Technical Questions & Answers

Frequently Asked Technical Questions

Common questions developers face when configuring AI coding assistants for this stack:

Q:Why is FastAPI Lifespan TaskGroup ExceptionGroup Drain Invariant critical for AI coding assistants?

Guarantees that background workers, message consumers, and persistent coroutines managed within FastAPI lifespan contexts utilize Python 3.12+ asyncio.TaskGroup with graceful cancellation and comprehensive ExceptionGroup unwinding. Prevents silent coroutine abandonment, zombie connection leaks, and SIGTERM worker deadlocks during graceful shutdown. Enforces clean context drain before the ASGI application server finalizes process termination. Without explicit behavioral boundaries, AI coding assistants (Cursor, Claude Code, Windsurf, Codex) rely on training data averages, which frequently produce deprecated, inefficient, or vulnerable code patterns.

Q:How does adopting this rule prevent AI hallucinations?

This rule explicitly declares what the AI must avoid (Fire-and-Forget asyncio.create_task Inside FastAPI Lifespan Without TaskGroup Drain or ExceptionGroup Handlers) and prescribes verified positive examples. Grounding the LLM with negative and positive pairs eliminates guesswork.

Q:Which AI coding environments are supported?

This specification is multi-platform compatible: Cursor (.cursor/rules/*.mdc & .cursorrules), Anthropic Claude Code (CLAUDE.md), Windsurf (.windsurfrules), Codex / Cline (AGENTS.md), and GitHub Copilot (.github/copilot-instructions.md).

Q:Can I combine this rule with other technology stacks?

Yes. Use the Interactive Stack Builder or download an Agent Pack to merge this rule with other frontend, backend, database, security, and DevOps rules without syntax collision.

Open Specification · CC BY 4.0·Free for Personal, Team & Commercial In-Repo Use

Citation & Open Source Attribution Guidelines

This specification is licensed under Creative Commons Attribution 4.0 International (CC BY 4.0). You are completely free to adapt and share this rule in your workspace. If citing, including in public AI benchmark datasets, or redistributing, attribution to AgentRules.org is required.

1 Rule in Stack

FastAPI