Skip to content

[Due for payment 2026-09-17] Client drops writes rejected with jsonCode 503 instead of retrying with backoff #100132

Description

@melvin-bot

Problem

When the backend sheds writes during instability, it answers with HTTP 200 and an app-level jsonCode: 503 — "This action is unavailable right now. Please try again shortly."

The client treats that as a final failure: failureData is applied, the request is removed from the persisted queue, and the user gets the red "couldn't be delivered" state on their message. No retry, no backoff. This is the one failure mode that explicitly asks the client to try again later, and it's the one that bypasses the retry path.

Why it happens

processHTTPRequest is what decides retry vs. final — SequentialQueue.process() only retries when the promise rejects. It throws for:

  • non-ok HTTP statuses (500 / 502 / 504 / unknown)
  • HTTP 429 (throttled)
  • jsonCode 400 + duplicate record
  • jsonCode 666 + already-created message
  • jsonCode 666 + socket title (Auth down/timed out)

A jsonCode 503 in a 200 body matches none of these, so the promise resolves, the queue removes the request as processed, and failureData lands on the message. There is no 503 handling anywhere in src/libs/Network.

Observed in production

2026-08-31, ~16:30–16:37 UTC. Two comments were queued in a Chronos chat during backend instability.

  1. The first hit a backend timeout (jsonCode 666). That rejects, so the client rolled back, backed off, retried once, and got a 200. Self-healed correctly.
  2. The second sat behind it in the SequentialQueue for ~7 minutes. When it finally went out it came back jsonCode 503 and was dropped, with the error stamped on the message. The user had no retry affordance — only the X to clear it.

Proposed fix

Treat jsonCode 503 like the other service-interruption cases: throw from processHTTPRequest so SequentialQueue rolls back the optimistic data and retries through RequestThrottle with exponential backoff. If the retry cap is exhausted, fall through to today's behavior and apply failureData.

Worth confirming with the backend team that 503 is only ever returned for load shedding. If it can also mean "permanently unavailable for this action", the client needs to distinguish the two before retrying blindly.

Reported in Slack.

Issue OwnerCurrent Issue Owner: @mallenexpensify

Activity

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

Metadata

Metadata

Labels

Type

No type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions