The Fix
Fixes a bug where file uploads fail with HTTP 422 errors when encountering 307/308 redirects by preserving the request body.
Based on closed aio-libs/aiohttp issue #11270 · PR/commit linked
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+Fixed file uploads failing with HTTP 422 errors when encountering 307/308 redirects, and 301/302 redirects for non-POST methods, by preserving the request body when appropriate per :rfc:`9110#section-15.4.3-3.1` -- by :user:`bdraco`.
diff --git a/aiohttp/client.py b/aiohttp/client.py
index 29c63a6b031..6a8c667491f 100644
from asyncio import run
from io import BufferedReader, BytesIO, FileIO, StringIO, TextIOWrapper
from typing import AsyncGenerator
from aiohttp import ClientSession, FormData
async def file_chunks(*args, **kwargs) -> AsyncGenerator[bytes, None]:
import aiofiles
async with aiofiles.open(*args, **kwargs) as file:
while chunk := await file.read(64*1024):
yield chunk
async def upload_file():
filename = "server.py"
# url = "http://localhost:8000/files" # all good, always works
url = "http://localhost:8000/files/" # doesn't work after the FastAPI redirect
with open(filename, "rb") as data:
# with FileIO(filename) as data:
# with BufferedReader(FileIO(filename)) as data:
# with TextIOWrapper(BufferedReader(FileIO(filename))) as data:
# with BytesIO(b"test") as data: # works for both urls
# with StringIO("test") as data: # works for both urls
formdata = FormData({"file": data}) # doesn't work with file streams
# formdata = FormData({"file": data.read()}) # works, but passing bytes is deprecated
# formdata = FormData(); formdata.add_field("file", file_chunks(filename, "rb"), filename=filename) # doesn't work with redirects as it is a generator
# formdata = None # works as there is no passed data
async with (
ClientSession() as session,
session.post(url, data=formdata) as resp,
):
print(await resp.text())
resp.raise_for_status()
if __name__ == "__main__":
run(upload_file())
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
Option A — Apply the official fix\nFixes a bug where file uploads fail with HTTP 422 errors when encountering 307/308 redirects by preserving the request body.\nWhen NOT to use: This fix should not be used if the application does not handle redirects correctly.\n\n
Why This Fix Works in Production
- Trigger: Server output:
- Mechanism: File uploads fail with HTTP 422 errors due to incorrect handling of request body during 307/308 redirects
- If left unfixed, this can cause silent data inconsistencies that propagate (bad cache entries, incorrect downstream decisions).
Why This Breaks in Prod
- Shows up under Python 3.10.11 in real deployments (not just unit tests).
- File uploads fail with HTTP 422 errors due to incorrect handling of request body during 307/308 redirects
- Surfaces as: Server output:
Proof / Evidence
- GitHub issue: #11270
- Fix PR: https://github.com/aio-libs/aiohttp/pull/11290
- Reproduced locally: No (not executed)
- Last verified: 2026-02-09
- Confidence: 0.80
- Did this fix it?: Yes (upstream fix exists)
- Own content ratio: 0.30
Discussion
High-signal excerpts from the issue thread (symptoms, repros, edge-cases).
“Oh, I didn't test the reproducer because its not a minimal one without external deps. I should do that”
“I have an xfail test... working on a fix”
“> Version: 3.12.7 Can you test with the latest release first? This is a new feature in 3.12 and has already received some fixes in…”
“> Can you test with the latest release first? > > This is a new feature in 3.12 and has already received some fixes in…”
Failure Signature (Search String)
- Server output:
Error Message
Stack trace
Error Message
-------------
Server output:
INFO: 127.0.0.1:65045 - "POST /files/ HTTP/1.1" 307 Temporary Redirect
WARNING: 127.0.0.1:65046 - "POST /files HTTP/1.1" 400 Bad Request
Client output:
Invalid HTTP request received.
Traceback (most recent call last):
File "C:\Users\Mike\Desktop\aiohttp_request.py", line 30, in <module>
run(upload_file())
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.3056.0_x64__qbz5n2kfra8p0\lib\asyncio\runners.py", line 44, in run
return loop.run_until_complete(main)
File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.10_3.10.3056.0_x64__qbz5n2kfra8p0\lib\asyncio\base_events.py", line 649, in run_until_complete
return future.result()
File "C:\Users\Mike\Desktop\aiohttp_request.py", line 26, in upload_file
resp.raise_for_status()
File "C:\Users\Mike\AppData\Local\Packages\PythonSoftwareFoundation.Python.3.10_qbz5n2kfra8p0\LocalCache\local-packages\Python310\site-packages\aiohttp\client_reqrep.py", line 625, in raise_for_status
raise ClientResponseError(
aiohttp.client_exceptions.ClientResponseError: 400, message='Bad Request', url='http://localhost:8001/files'
Minimal Reproduction
from asyncio import run
from io import BufferedReader, BytesIO, FileIO, StringIO, TextIOWrapper
from typing import AsyncGenerator
from aiohttp import ClientSession, FormData
async def file_chunks(*args, **kwargs) -> AsyncGenerator[bytes, None]:
import aiofiles
async with aiofiles.open(*args, **kwargs) as file:
while chunk := await file.read(64*1024):
yield chunk
async def upload_file():
filename = "server.py"
# url = "http://localhost:8000/files" # all good, always works
url = "http://localhost:8000/files/" # doesn't work after the FastAPI redirect
with open(filename, "rb") as data:
# with FileIO(filename) as data:
# with BufferedReader(FileIO(filename)) as data:
# with TextIOWrapper(BufferedReader(FileIO(filename))) as data:
# with BytesIO(b"test") as data: # works for both urls
# with StringIO("test") as data: # works for both urls
formdata = FormData({"file": data}) # doesn't work with file streams
# formdata = FormData({"file": data.read()}) # works, but passing bytes is deprecated
# formdata = FormData(); formdata.add_field("file", file_chunks(filename, "rb"), filename=filename) # doesn't work with redirects as it is a generator
# formdata = None # works as there is no passed data
async with (
ClientSession() as session,
session.post(url, data=formdata) as resp,
):
print(await resp.text())
resp.raise_for_status()
if __name__ == "__main__":
run(upload_file())
Environment
- Python: 3.10.11
What Broke
Users experience 422 Unprocessable Entity errors when uploading files after a redirect.
Why It Broke
File uploads fail with HTTP 422 errors due to incorrect handling of request body during 307/308 redirects
Fix Options (Details)
Option A — Apply the official fix
Fixes a bug where file uploads fail with HTTP 422 errors when encountering 307/308 redirects by preserving the request body.
Fix reference: https://github.com/aio-libs/aiohttp/pull/11290
Last verified: 2026-02-09. Validate in your environment.
When NOT to Use This Fix
- This fix should not be used if the application does not handle redirects correctly.
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.
Related Issues
No related fixes found.
Sources
We don’t republish the full GitHub discussion text. Use the links above for context.