Jump to solution
Verify

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

Jump to Verify Open PR/Commit
@@ -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
repro.py
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())
verify
Re-run the minimal reproduction on your broken version, then apply the fix and re-run.
fix.md
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
Production impact:
  • 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

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”
@bdraco · 2025-07-09 · repro detail · source
“I have an xfail test... working on a fix”
@bdraco · 2025-07-09 · source
“> 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…”
@Dreamsorcerer · 2025-07-08 · source
“> Can you test with the latest release first? > > This is a new feature in 3.12 and has already received some fixes in…”
@mberdyshev · 2025-07-09 · source

Failure Signature (Search String)

  • Server output:

Error Message

Stack trace
error.txt
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

repro.py
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.

When NOT to use: This fix should not be used if the application does not handle redirects correctly.

Fix reference: https://github.com/aio-libs/aiohttp/pull/11290

Last verified: 2026-02-09. Validate in your environment.

Get updates

We publish verified fixes weekly. No spam.

Subscribe

When NOT to Use This Fix

  • This fix should not be used if the application does not handle redirects correctly.

Verify Fix

verify
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.