Show the relay's sentence, not its JSON

Creating a group with a member who has never signed in put this on screen:

    relay returned 400: {"code":"bad_request","message":"No reachable
    device for user: 30.","data":{"status":400}}

The relay had written a perfectly readable sentence and the app showed the
envelope around it. Found by creating a group on an emulator, not by reading
code.

⚠️ The REFUSAL is correct — requireReachable declines a group whose member has
no device, because there is nothing to encrypt to. Only the presentation was
wrong.

Cause: MessengerApi.send and RelayAccounts already unwrap `message` from the
error body; RelayClient.checked did not. So which client a feature happened to
be built on decided whether its errors were legible, which is not a decision
anyone made. Now all three unwrap.

182 Android unit tests still green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Paul Hitt
2026-08-25 20:00:45 -04:00
co-authored by Claude Opus 5
parent 2855711bc5
commit b051814a6c
@@ -421,11 +421,26 @@ class RelayClient(
val body = response.body?.string().orEmpty()
val code = response.code
response.close()
// ⚠️ **Carry the relay's own sentence, not its JSON.** These messages
// reach the screen: creating a group with a member who has never signed
// in showed the user
//
// relay returned 400: {"code":"bad_request","message":"No reachable
// device for user: 30.","data":{"status":400}}
//
// when the relay had written a perfectly readable sentence inside it.
// `MessengerApi.send` and `RelayAccounts` already unwrap `message`;
// this path did not, so which client a feature happened to use decided
// whether its errors were legible.
val readable = runCatching { JSONObject(body).optString("message") }
.getOrNull().orEmpty().ifBlank { body }
throw when (code) {
401 -> RelayException.Unauthorized(body)
401 -> RelayException.Unauthorized(readable)
404 -> RelayException.NotFound(path)
429 -> RelayException.RateLimited(response.header("Retry-After")?.toIntOrNull())
else -> RelayException.Http(code, body)
else -> RelayException.Http(code, readable)
}
}