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

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions