fix(worker): treat Meta code 1 as delivery-unconfirmed, not a plain failure - #70
Open
valentinpanizza wants to merge 1 commit into
Open
valentinpanizza wants to merge 1 commit into
valentinpanizza wants to merge 1 commit into
Conversation
|
@valentinpanizza is attempting to deploy a commit to the diwenne's projects Team on Vercel. A member of the Team first needs to authorize it. |
…ailure
Meta answers some sends on /messages with the generic code 1 OAuthException
*after* the DM has already reached the recipient. Observed in production: a
user tapped the button of a reply this worker had just marked FAILED, 30
seconds earlier.
Logging that as a plain failure caused duplicate sends through two separate
paths that compounded each other:
* the job was retried up to 3 times (5/15/45 min backoff), and every retry
delivered another copy to the same inbox;
* the DmLog row never satisfied the reconciler's handled test
(status SENT or dmDeliveryUnconfirmed), so each five-minute sweep
re-enqueued the same comment for the whole lookback window.
Together those sent single recipients dozens of identical DMs, with only one
DmLog row to show for it because the row is updated rather than inserted.
isDeliveryUnconfirmed() now covers code 1 alongside the Zernio case, and is
used both for the stored flag and for the retry decision in processJob, so an
ambiguous send is recorded once and never repeated. The public-reply failure
path gets the same treatment, since the sweep's dedup reads that flag too.
The trade-off is deliberate: a code 1 that really did fail now means the
person receives no DM and can comment again, which is far better than sending
more copies to someone who already received it.
valentinpanizza
force-pushed
the
fix/meta-code-1-duplicate-sends
branch
from
September 23, 2026 00:11
87fa4e9 to
eaf8d69
Compare
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.
What happens
A single commenter can receive the same DM dozens of times.
In production on my instance, 11 people got between 3 and ~40 copies of the same
private reply over a few hours, and one of them messaged me asking me to stop.
DmLogshowed one row each, so nothing looked wrong until I checked the workerlogs: 828
failed (attempt N)lines, all the same error.Root cause
Meta answers some sends on
/messageswith the generic code 1 OAuthExceptionafter the DM has already been delivered. What made me certain it delivers: a user
tapped the button of a reply the worker had marked
FAILED30 seconds earlier —they cannot tap a button in a message they never received.
Recording that as a plain failure feeds two amplifiers that compound:
Retries.
processJobonly treatsZernioDeliveryUnconfirmedErrorasunrecoverable, so a
MetaApiErroris retried up toattempts: 3with the5/15/45-minute backoff. Every retry delivers another copy.
The reconciler.
reconcileCommentsconsiders a comment handled only whenits
DmLogrow isstatus: "SENT"ordmDeliveryUnconfirmed: true. AFAILEDrow satisfies neither, so every five-minute sweep re-enqueues the same comment
for the entire
COMMENT_POLL_LOOKBACK_HOURSwindow.Roughly 30 re-enqueues x 3 attempts each. The damage scales with the lookback
window: I had it at 3 hours, which capped it near 40 copies. At the default 72
hours the same bug would send on the order of 850.
This is also why it is easy to miss in the data —
DmLogis unique onautomationId_commentId, so every repeat updates the existing row instead ofinserting one. Row counts look clean; only
updatedAtdrifting hours pastcreatedAtgives it away.The fix
isDeliveryUnconfirmed()now covers Meta code 1 alongside the existing Zerniocase, and is used in both places that matter:
dmDeliveryUnconfirmed/publicReplyDeliveryUnconfirmedflags, sothe sweep's dedup treats the comment as handled;
processJob, so the job is not attempted again.That is what the
dmDeliveryUnconfirmedcolumn already exists for, andprocessCommenthonours it (needsDm = !alreadyDmd && !existingLog?.dmDeliveryUnconfirmed),so an ambiguous send is now recorded once and never repeated.
Left alone deliberately:
ZernioDeliveryUnconfirmedError, so it is covered;behaviour — routing code 1 through
isDeliveryUnconfirmedthere would make itfall through to the failure path instead.
Trade-off
A code 1 that genuinely did fail now means that person receives no DM and can
comment again, instead of getting more copies of a message they already have. For
a tool whose whole job is DMing strangers, erring toward under-delivery seems
clearly right.
Verification
Deployed on my instance: 0 failed attempts since, and the ~20 already-queued
delayed jobs drained without sending anything, because
processCommentsaw theflag and skipped them.