Repository navigation
Conversation
…nce errors Store the gateway's own error (bounded, well-formed) on failed messages, retry 422 balance errors instead of failing them, fail invalid_number and unknown responses instead of marking them sent or retrying forever.
Per the maintainer's review: a rotated or expired key is account-level like a balance error, and a missing invalid_number array no longer fails every message; both are logged as warnings.
binokaryg
self-requested a review
October 16, 2026 21:31
binokaryg
requested changes
Oct 16, 2026
binokaryg
left a comment
Member
There was a problem hiding this comment.
Thanks @dacar1988 for taking this on.
The five rules from the issue are all implemented and tested, the two follow-ups from the 15th are in, and the spec covers the production shapes well. One retry decision goes the wrong way for a case the gateway has already produced; that is the only thing I would hold on. The rest are suggestions, all inline.
…account-level errors once Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…car1988/cht-core into 11438-doit-sms-failure-reason
Contributor
Author
|
Thanks @binokaryg, all of it is in the latest commit:
|
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Problem. The Nepal DoIT SMS service (
api/src/services/nepal-doit-sms.js) stored a fixed label per HTTP status, so every422was recorded asUnprocessableContentand treated as final. ABalance not enoughoutage therefore failed messages permanently, and nothing on the doc said why. Two more cases were wrong: a200withstatus: 1and a non-emptyinvalid_numberwas markedsent, and a200JSON reply with an unrecognisedstatuswas retried forever (and enough of those block the 25-message queue).What changed (one file; the HTTP request itself is unchanged). The rules follow @binokaryg's answers on the issue (a month of production logs + live API probes):
state_details422with a stringerrorsmatching/balance/i422(incl.errors.mobilevalidation, i.e. unresolved recipients)failed{ code: 422, reason: 'UnprocessableContent', error }401(rotated/expired key: account-level, like balance)500,504, other HTTP errors, network/JSON-parse errors200,status: 1(or'1'), empty or missinginvalid_numbersent(a warning is logged when it's missing or not an array)'Processed'(as before)200,status: 1(or'1'), non-emptyinvalid_numberfailed{ code: 200, reason: 'InvalidNumber', error: '["98…"]' }200, a JSON object with any otherstatus(unknown or missing)failed{ code: 200, reason: 'InvalidResponse', error: <body excerpt> }200, a body that is not a JSON object (HTML page, empty or text body)erroris the gateway's ownerrors/messagetext (or a body excerpt), cut to 255 UTF-16 units and made well-formed withtoWellFormed()(Node ≥ 22.15 perengines). A split surrogate pair would make CouchDB reject the whole batch write (invalid UTF-8 JSON), losing every state change in it and resending those messages each minute; we reproduced that and guard it with tests. Error log lines now include the message id; account-level retries (balance,401) are logged once, atwarn, instead of an error line plus a warning.Choices worth flagging
statusexactly1or'1'(master matched both;trueor' 1 'are not accepted). Per @binokaryg's follow-up, a missing or non-arrayinvalid_numberstill counts as success (with a warning), so a gateway format change can't fail every message; only a non-empty array fails.401is now retried (with a warning) instead of failed.200whose body is not a JSON object (a proxy, maintenance or nginx page; couch-request returns those as text) is not a gateway verdict, so it is retried like the504page, as @binokaryg asked in review.application/jsonbody still throws inside couch-request before we see it, so it stays on the retry path, like network errors.Invalid number.test data comes from the DoIT guide; per the maintainer it never occurs in production. The production-backed cases areerrors.mobileand theinvalid_numberresponse.messaging.outgoing.gateway) /GET /api/balance. Messages alreadyfailedby the old code are not retried automatically.How it was tested
api/tests/mocha/services/nepal-doit-sms.spec.js(43 tests), e.g.retries a 422 balance error instead of failing the message,matches any balance error case-insensitively,fails a number the gateway lists as invalid, logging the message id(the maintainer's live response),fails any other status in a successful response, without mapping it to an HTTP status,keeps recorded response bodies bounded and well-formed,stores a well-formed error when the cut splits a character, whatever the error source,still retries a 504 gateway page. On master's service 36 of them fail; each new rule was also checked against deliberately broken variants.npm run lintand all unit suites (webapp, admin, shared-libs, api, sentinel, couchdb) pass.sent, balance messages stayfailedafter the top-up, unknown/empty200s are retried forever. After: balance messages aresentafter the top-up, everything terminal is attempted once, a successful sibling is sent exactly once, no CouchDB errors.sentexactly once (happy path); a message sent to a contact whose number the (fake) gateway rejects with the maintainer'sinvalid_numberresponse ends upfailed(InvalidNumber) in the conversation view; on master it showssent.before

after admin queue 18 sms

user message failed

Note: the screenshots predate the last two commits; since then a
401and a non-JSON200page are retried (the message stays pending) instead of failed.Closes #11438
Code review checklist
AI Disclosure: Research, plan, tests and implementation were produced with Claude Code (Opus), with Codex (GPT) implementing parts and reviewing the research, plan, tests and diff, under my direction; I reviewed the plan, the diff and ran the manual checks myself.
🤖 Generated with Claude Code
License
The software is provided under AGPL-3.0. Contributions to this project are accepted under the same license.