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.
- 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.
- 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 Owner
Current Issue Owner: @mallenexpensify
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:
failureDatais 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
processHTTPRequestis what decides retry vs. final —SequentialQueue.process()only retries when the promise rejects. It throws for:jsonCode 400+ duplicate recordjsonCode 666+ already-created messagejsonCode 666+ socket title (Auth down/timed out)A
jsonCode 503in a 200 body matches none of these, so the promise resolves, the queue removes the request as processed, andfailureDatalands on the message. There is no503handling anywhere insrc/libs/Network.src/libs/HttpUtils.tssrc/libs/Network/SequentialQueue.tsObserved in production
2026-08-31, ~16:30–16:37 UTC. Two comments were queued in a Chronos chat during backend instability.
jsonCode 666). That rejects, so the client rolled back, backed off, retried once, and got a 200. Self-healed correctly.jsonCode 503and 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 503like the other service-interruption cases: throw fromprocessHTTPRequestsoSequentialQueuerolls back the optimistic data and retries throughRequestThrottlewith exponential backoff. If the retry cap is exhausted, fall through to today's behavior and applyfailureData.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 Owner
Current Issue Owner: @mallenexpensify