Skip to content

KeyError: 'response' in _error_wrapper when using moto + aiobotocore 3.x #1011

Description

@cvjjm

When using s3fs with aiobotocore 3.x and moto (mock_aws), any write operation raises KeyError: "local variable ''response'' is not defined" from inside s3fs.core._error_wrapper. The real underlying error is masked entirely.

I see the following traceback:

File ".../s3fs/core.py", line 214, in _error_wrapper
    raise err
File ".../s3fs/core.py", line 210, in _error_wrapper
    await tb.tb_frame.f_locals"response"
KeyError: "local variable ''response'' is not defined"
RuntimeWarning: coroutine 'AioAwsChunkedWrapper.read' was never awaited

with versions:

s3fs 2026.2.0
aiobotocore 3.2.0
moto 5.1.21
botocore 1.42.55
boto3 1.42.55
Python 3.13

Root cause

_error_wrapper contains this recovery block for 'coroutine'-related errors:

if "'coroutine'" in str(err):
    tb = err.__traceback__
    while tb.tb_next:
        tb = tb.tb_next
    try:
        await tb.tb_frame.f_locals["response"]   # <-- no guard
    except Exception as e:
        err = e

It unconditionally accesses tb.tb_frame.f_locals["response"]. With real aiohttp the innermost frame always contains a response local, but when moto intercepts HTTP calls the innermost frame is inside moto/s3/responses.py:_handle_encoded_body, which has no response variable. The KeyError replaces the original error.

The 'coroutine'-related error that triggers this block is caused by a separate moto bug (moto's BotocoreStubber calls body.readline() synchronously on aiobotocore 3.x's async AioAwsChunkedWrapper), but the missing guard might independently be a potential problem.

Minimal reproducer

import os, boto3, s3fs
from moto import mock_aws
os.environ["AWS_ACCESS_KEY_ID"] = "testing"
os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
os.environ["AWS_DEFAULT_REGION"] = "us-east-1"
@mock_aws
def test():
    boto3.client("s3", region_name="us-east-1").create_bucket(Bucket="test-bucket")
    fs = s3fs.S3FileSystem(anon=False)
    with fs.open("test-bucket/hello.txt", "w") as f:
        f.write("hello world")
test()

A possible fix could be:

if "'coroutine'" in str(err):
    tb = err.__traceback__
    while tb.tb_next:
        tb = tb.tb_next
    if "response" in tb.tb_frame.f_locals:   # guard added
        try:
            await tb.tb_frame.f_locals["response"]
        except Exception as e:
            err = e

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions