Skip to content

fix(functions): keep edge function failures as FunctionsHttpError - #1576

Open
tushardev-365 wants to merge 1 commit into
supabase:mainfrom
tushardev-365:fix/functions-non-json-error
Open

fix(functions): keep edge function failures as FunctionsHttpError#1576
tushardev-365 wants to merge 1 commit into
supabase:mainfrom
tushardev-365:fix/functions-non-json-error

Conversation

@tushardev-365

Copy link
Copy Markdown
Contributor

What

_request reads the failure body inside its except HTTPError handler:

raise FunctionsHttpError(
    response.json().get("error")
    or f"An error occurred while requesting your edge function at {exc.request.url!r}.",
    status_code,
) from exc

A failing edge function does not always answer with a JSON object. A 502/504 from the gateway returns HTML, and a body can be a valid JSON array or string. So:

non-JSON body   -> json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
JSON list body  -> AttributeError: 'list' object has no attribute 'get'

The caller gets that raw exception instead of FunctionsHttpError, so except FunctionsHttpError around invoke() does not catch the failure, and the status code and body are lost.

There is a second effect worth calling out: the or "An error occurred ..." fallback was unreachable in exactly the situation it was written for. It could only be reached once .json() had already succeeded and returned a dict — i.e. never for the malformed bodies it was meant to cover.

The relay path had the identical call:

raise FunctionsRelayError(response.json().get("error"))

Fix

Read the body through a small get_error_message helper in utils.py that returns None when the body is not JSON or not an object, so the existing fallback message does its job. Applied to both sites.

Tests

Two cases per flavour (async + sync): a non-JSON error body and a JSON body that is not an object. Both assert FunctionsHttpError. Reverting only the client call sites makes all four fail with JSONDecodeError / AttributeError.

Full functions suite: 69 passed, 1 skipped. _sync is generated, so the change was made in _async and regenerated with make build-sync.

The error handler read the failure body with response.json().get("error")
inside except HTTPError. An edge function that fails at the gateway does
not necessarily answer with a JSON object: a 502 or 504 typically returns
HTML, and a body can be a valid JSON array or string. In those cases the
handler raised JSONDecodeError or AttributeError, so callers wrapping the
call in except FunctionsHttpError never saw the failure, and the status
code and body were lost.

The existing fallback message was unreachable for exactly the case it was
written for, because it could only be selected once .json() had already
succeeded and returned a dict.

Read the body through a small helper that returns None when it is not a
JSON object, so the fallback works as intended. Same handling for the
relay error path, which had the identical call.
@tushardev-365
tushardev-365 requested review from a team and o-santi as code owners August 15, 2026 05:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant