Skip to content

Conversation

@faroukBakari
Copy link

Summary

This PR fixes the long-standing issue where custom 500 and Exception handlers are excluded from ExceptionMiddleware, causing them to bypass the user middleware stack (including CORSMiddleware).

Problem

Currently in build_middleware_stack(), handlers for 500 and Exception are special-cased to only pass to ServerErrorMiddleware and are excluded from ExceptionMiddleware:

for key, value in self.exception_handlers.items():
    if key in (500, Exception):
        error_handler = value
    else:
        exception_handlers[key] = value  # 500/Exception never added here

This causes:

  • CORS headers missing on 500 responses (browsers can't read error details)
  • Logging/monitoring middleware cannot track 500 errors
  • Correlation ID middleware headers stripped from error responses
  • Inconsistent behavior: 404 errors pass through middleware, 500 errors don't

Solution

Remove the else clause so 500/Exception handlers are passed to both middlewares:

for key, value in self.exception_handlers.items():
    if key in (500, Exception):
        error_handler = value
    exception_handlers[key] = value  # Now always added

This allows:

  1. ExceptionMiddleware to handle the exception first (inside user middleware stack)
  2. ServerErrorMiddleware to act as a fallback for truly unhandled exceptions

Related Issues

This addresses the root cause of many long-standing issues:

Starlette:

FastAPI (downstream impact):

Backward Compatibility

  • Debug mode: ServerErrorMiddleware still provides debug tracebacks as fallback
  • No handler registered: Behavior unchanged (ServerErrorMiddleware handles it)
  • Handler registered: Now runs through middleware stack first, with ServerErrorMiddleware as safety net

@Kludex Kludex closed this Dec 16, 2025
@Kludex
Copy link
Owner

Kludex commented Dec 16, 2025

This is not a bug.

All the pointed issues are closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment