The Fix
pip install fastapi==0.128.4
Based on closed fastapi/fastapi issue #14454 · PR/commit linked
Production note: This usually shows up under retries/timeouts. Treat it as a side-effect risk until you can verify behavior with a canary + real traffic.
@@ -2,7 +2,7 @@
from dataclasses import dataclass, field
from functools import cached_property, partial
-from typing import Any, Callable, List, Optional, Sequence, Union
+from typing import Any, Callable, List, Optional, Union
# /// script
# dependencies = [
# "fastapi[standard]==0.121.3",
# ]
# ///
import json
import sys
from typing import Annotated
import fastapi
from fastapi import APIRouter, Depends, FastAPI, Security
from fastapi.security import OAuth2AuthorizationCodeBearer, SecurityScopes
# print the dependencies
print(f'fastapi version = {fastapi.__version__}')
print(f'Python version = {sys.version}')
app = FastAPI()
oauth2_scheme = OAuth2AuthorizationCodeBearer(
authorizationUrl='api/oauth/authorize',
tokenUrl='/api/oauth/token',
refreshUrl='/api/oauth/token',
auto_error=False,
scopes={'read': 'Read access', 'write': 'Write access'},
)
async def get_token(
token: Annotated[str, Depends(oauth2_scheme)],
) -> str:
return token
AccessToken = Annotated[str, Depends(get_token, use_cache=True)]
async def require_oauth_scopes(security_scopes: SecurityScopes, token: AccessToken) -> None:
pass
async def check_limit(token: AccessToken) -> None:
pass
# The problem is here. If we remove the dependency from the router, the schema is generated correctly in both fastapi versions.
router = APIRouter(prefix='/v1', dependencies=[Depends(check_limit)])
channels_router = APIRouter(prefix='/channels', tags=['Channels'])
@channels_router.get("/", dependencies=[Security(require_oauth_scopes, scopes=['read'])])
def read_items():
return {"msg": "You have READ access"}
router.include_router(channels_router)
app.include_router(router)
schema = app.openapi()
pretty_schema = json.dumps(schema, indent=4)
print(pretty_schema)
"""
with fastapi 0.123.7, the generated openapi schema icludes security without scopes:
"security": [
{
"OAuth2AuthorizationCodeBearer": []
}
]
but in fastapi 0.121.3, the generated openapi schema includes two items in security:
"security": [
{
"OAuth2AuthorizationCodeBearer": []
},
{
"OAuth2AuthorizationCodeBearer": [
"read"
]
}
]
"""
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Option A — Upgrade to fixed release\npip install fastapi==0.128.4\nWhen NOT to use: This fix should not be used if the application relies on the previous behavior of duplicated security schemes.\n\n
Why This Fix Works in Production
- Trigger: OAuth2 security schemes duplicated in OpenAPI, with and without scopes, when used at the router level
- Mechanism: OAuth2 security schemes were incorrectly duplicated in OpenAPI due to dependency handling
- Why the fix works: Fix OAuth2 scopes in OpenAPI in extra corner cases, parent dependency with scopes, sub-dependency security scheme without scopes. (first fixed release: 0.128.4).
- If left unfixed, this can cause silent data inconsistencies that propagate (bad cache entries, incorrect downstream decisions).
Why This Breaks in Prod
- OAuth2 security schemes were incorrectly duplicated in OpenAPI due to dependency handling
- Production symptom (often without a traceback): OAuth2 security schemes duplicated in OpenAPI, with and without scopes, when used at the router level
Proof / Evidence
- GitHub issue: #14454
- Fix PR: https://github.com/fastapi/fastapi/pull/14459
- First fixed release: 0.128.4
- Reproduced locally: No (not executed)
- Last verified: 2026-02-08
- Confidence: 0.85
- Did this fix it?: Yes (upstream fix exists)
- Own content ratio: 0.41
Verified Execution
We executed the runnable minimal repro in a temporary environment and captured exit codes + logs.
- Status: PASS
- Ran: 2026-02-11T16:52:29Z
- Package: fastapi
- Fixed: 0.128.4
- Mode: fixed_only
- Outcome: ok
Logs
fastapi version = 0.128.4
Python version = 3.10.12 (main, Jan 26 2026, 14:55:28) [GCC 11.4.0]
{
"openapi": "3.1.0",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/v1/channels/": {
"get": {
"tags": [
"Channels"
],
"summary": "Read Items",
"operationId": "read_items_v1_channels__get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
},
"security": [
{
"OAuth2AuthorizationCodeBearer": [
"read"
]
}
]
}
}
},
"components": {
"securitySchemes": {
"OAuth2AuthorizationCodeBearer": {
"type": "oauth2",
"flows": {
"authorizationCode": {
"refreshUrl": "/api/oauth/token",
"scopes": {
"read": "Read access",
"write": "Write access"
},
"authorizationUrl": "api/oauth/authorize",
"tokenUrl": "/api/oauth/token"
}
}
}
}
}
}
Discussion
High-signal excerpts from the issue thread (symptoms, repros, edge-cases).
“This was fixed by https://github.com/fastapi/fastapi/pull/14455, released in FastAPI 0.123.8 :tada:”
“This should be fixed now in https://github.com/fastapi/fastapi/pull/14459 (strike 2 ⚾). Released in FastAPI 0.123.9 🚀”
“That didn't fix it. 😱 Working on it now.”
Failure Signature (Search String)
- OAuth2 security schemes duplicated in OpenAPI, with and without scopes, when used at the router level
- authorizationUrl='api/oauth/authorize',
Copy-friendly signature
Failure Signature
-----------------
OAuth2 security schemes duplicated in OpenAPI, with and without scopes, when used at the router level
authorizationUrl='api/oauth/authorize',
Error Message
Signature-only (no traceback captured)
Error Message
-------------
OAuth2 security schemes duplicated in OpenAPI, with and without scopes, when used at the router level
authorizationUrl='api/oauth/authorize',
Minimal Reproduction
# /// script
# dependencies = [
# "fastapi[standard]==0.121.3",
# ]
# ///
import json
import sys
from typing import Annotated
import fastapi
from fastapi import APIRouter, Depends, FastAPI, Security
from fastapi.security import OAuth2AuthorizationCodeBearer, SecurityScopes
# print the dependencies
print(f'fastapi version = {fastapi.__version__}')
print(f'Python version = {sys.version}')
app = FastAPI()
oauth2_scheme = OAuth2AuthorizationCodeBearer(
authorizationUrl='api/oauth/authorize',
tokenUrl='/api/oauth/token',
refreshUrl='/api/oauth/token',
auto_error=False,
scopes={'read': 'Read access', 'write': 'Write access'},
)
async def get_token(
token: Annotated[str, Depends(oauth2_scheme)],
) -> str:
return token
AccessToken = Annotated[str, Depends(get_token, use_cache=True)]
async def require_oauth_scopes(security_scopes: SecurityScopes, token: AccessToken) -> None:
pass
async def check_limit(token: AccessToken) -> None:
pass
# The problem is here. If we remove the dependency from the router, the schema is generated correctly in both fastapi versions.
router = APIRouter(prefix='/v1', dependencies=[Depends(check_limit)])
channels_router = APIRouter(prefix='/channels', tags=['Channels'])
@channels_router.get("/", dependencies=[Security(require_oauth_scopes, scopes=['read'])])
def read_items():
return {"msg": "You have READ access"}
router.include_router(channels_router)
app.include_router(router)
schema = app.openapi()
pretty_schema = json.dumps(schema, indent=4)
print(pretty_schema)
"""
with fastapi 0.123.7, the generated openapi schema icludes security without scopes:
"security": [
{
"OAuth2AuthorizationCodeBearer": []
}
]
but in fastapi 0.121.3, the generated openapi schema includes two items in security:
"security": [
{
"OAuth2AuthorizationCodeBearer": []
},
{
"OAuth2AuthorizationCodeBearer": [
"read"
]
}
]
"""
What Broke
OpenAPI schema generated with duplicate security schemes, causing confusion in API documentation.
Why It Broke
OAuth2 security schemes were incorrectly duplicated in OpenAPI due to dependency handling
Fix Options (Details)
Option A — Upgrade to fixed release Safe default (recommended)
pip install fastapi==0.128.4
Use when you can deploy the upstream fix. It is usually lower-risk than long-lived workarounds.
Fix reference: https://github.com/fastapi/fastapi/pull/14459
First fixed release: 0.128.4
Last verified: 2026-02-08. Validate in your environment.
When NOT to Use This Fix
- This fix should not be used if the application relies on the previous behavior of duplicated security schemes.
Verify Fix
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Did This Fix Work in Your Case?
Quick signal helps us prioritize which fixes to verify and improve.
Prevention
- Capture the exact failing error string in logs and tests so you can reproduce via a minimal script.
- Pin production dependencies and upgrade only with a reproducible test that hits the failing path.
Version Compatibility Table
| Version | Status |
|---|---|
| 0.128.4 | Fixed |
Related Issues
No related fixes found.
Sources
We don’t republish the full GitHub discussion text. Use the links above for context.