Skip to content

JSONDecodeError traceback shown on empty 429 response bodies #108

Description

@roi-shikler-thenvoi

Summary

The thenvoi_rest SDK throws a confusing JSONDecodeError when the API returns a 429 response with an empty body. While the SDK does eventually raise the correct ApiError, the intermediate JSONDecodeError traceback is displayed due to Python's exception chaining, creating noisy output.

Environment

  • Python 3.12
  • thenvoi-sdk-python (latest)
  • Server returning 429 from AWS ALB with empty body (Content-Length: 0)

Actual Behavior

Traceback (most recent call last):
  File ".../thenvoi_rest/agent_api/raw_client.py", line 2987, in get_agent_me
    _response_json = _response.json()
  ...
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  ...
thenvoi_rest.core.api_error.ApiError: status_code: 429, body:

Expected Behavior

A clean ApiError with no intermediate exception traceback.

Root Cause

In raw_client.py (lines 2955-2990), the code pattern is:

try:
    if 200 <= _response.status_code < 300:
        return parse_response(_response.json())
    if _response.status_code == 401:
        raise UnauthorizedError(body=_response.json())
    if _response.status_code == 403:
        raise ForbiddenError(body=_response.json())
    _response_json = _response.json()  # <-- Fails here for 429 with empty body
except JSONDecodeError:
    raise ApiError(...)  # Exception chaining shows both tracebacks
  • 429 doesn't match the explicit handlers, so falls through to JSON parsing
  • Empty body causes JSONDecodeError
  • Python's exception chaining displays both tracebacks

Note: thenvoi_rest is auto-generated by Fern, so fixes must go in Fern templates.

Proposed Fix

Option 1: Fern template fix - Add from None (Recommended)

except JSONDecodeError:
    raise ApiError(...) from None  # Suppress chained exception

Option 2: Early exit for error responses

if _response.status_code >= 400:
    try:
        body = _response.json()
    except JSONDecodeError:
        body = _response.text
    raise ApiError(status_code=..., body=body)

Impact

  • Severity: Low (functional behavior is correct, UX is poor)
  • Scope: All 32 API methods in raw_client.py follow this pattern

Activity

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

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions