Let the polling sweep see the ads a post was boosted into - #41
Conversation
Meta does not deliver every comment webhook, which is the whole reason the sweep exists — but the sweep only ever looked at the campaign's own post. Comments left on an ad carry the ad's own media id, so they fell outside it and were lost for good. This is not theoretical. On one boosted post, three of six keyword comments never produced a webhook; two of them were from this morning and had to be re-enqueued by hand. Boosting is also when comment volume is highest, so the safety net was missing precisely when it mattered. The ad ids come from the webhooks already received rather than from the ads API, which would need ads_management on top of the permissions the app asks for today. The trade-off is that an ad becomes visible to the sweep only after one comment on it has arrived — enough for this failure, where some webhooks arrive and others do not. A failed lookup returns nothing and leaves the sweep to check the post as before.
Five cases: the ad is returned, the post itself never is (it would be swept twice), rows without an id are dropped, an unboosted post yields nothing, and a failed query returns empty so the sweep still checks the post.
|
@emanueletech is attempting to deploy a commit to the diwenne's projects Team on Vercel. A member of the Team first needs to authorize it. |
The sweep learned to look at ad media this morning, but enqueued those comments with only the ad's media id. The worker then looked for a campaign bound to that id, found none, and dropped the comment — so the sweep re-enqueued the same comment every five minutes and never delivered anything. Four sweeps in a row reported "1 enqueued" while DmLog stayed empty. Found by testing on the live account rather than by reading the code: a comment posted from another account was picked up, enqueued and silently discarded, four times.
|
Pushed a follow-up: the first version enqueued ad comments with only the ad's media id, so the worker looked for a campaign bound to that id, found none, and dropped the comment. The sweep then re-enqueued the same comment every five minutes and delivered nothing — four sweeps in a row logging
Worth saying how it surfaced, since it says something about the change: the unit tests passed, the SQL was verified against a live database, and the lookup returned the right ad id — the defect was one layer further on, in what the sweep did with that id. It only showed up by posting a real comment from another account and watching it go nowhere. If you would rather see a test that covers the enqueue payload rather than just the lookup, say so and I will add it. |
diwenne
left a comment
There was a problem hiding this comment.
Verified locally: typecheck 0, lint 0, 168 tests (5 new adMediaFor cases) all pass. adMediaFor correctly sources ad IDs from WebhookEvent original_media_id (90d window), filters post itself + nulls, fail-open. sweepCampaign now sweeps ad media alongside post, and enqueues with originalMediaId so worker OR-match (mediaId/originalMediaId) from #25 routes ad comments to post-bound campaign. Fixes silent loss on boosted posts where webhooks are best-effort - second follow-up (fd1e17b) correctly addresses re-enqueue-every-5m worker drop.
* Let the polling sweep see the ads a post was boosted into Meta does not deliver every comment webhook, which is the whole reason the sweep exists — but the sweep only ever looked at the campaign's own post. Comments left on an ad carry the ad's own media id, so they fell outside it and were lost for good. This is not theoretical. On one boosted post, three of six keyword comments never produced a webhook; two of them were from this morning and had to be re-enqueued by hand. Boosting is also when comment volume is highest, so the safety net was missing precisely when it mattered. The ad ids come from the webhooks already received rather than from the ads API, which would need ads_management on top of the permissions the app asks for today. The trade-off is that an ad becomes visible to the sweep only after one comment on it has arrived — enough for this failure, where some webhooks arrive and others do not. A failed lookup returns nothing and leaves the sweep to check the post as before. * Cover the ad-media lookup with tests Five cases: the ad is returned, the post itself never is (it would be swept twice), rows without an id are dropped, an unboosted post yields nothing, and a failed query returns empty so the sweep still checks the post. * Tell the worker which post an ad comment belongs to The sweep learned to look at ad media this morning, but enqueued those comments with only the ad's media id. The worker then looked for a campaign bound to that id, found none, and dropped the comment — so the sweep re-enqueued the same comment every five minutes and never delivered anything. Four sweeps in a row reported "1 enqueued" while DmLog stayed empty. Found by testing on the live account rather than by reading the code: a comment posted from another account was picked up, enqueued and silently discarded, four times.
The bug
Boost a post and some of its comments stop being answered — silently, and only some of them, which is what makes it hard to notice.
The reconciler exists because Instagram webhooks are best-effort. But it only ever looks at the campaign's own post, and a comment left on an ad carries the ad's media id, not the post's. So when Meta drops a webhook for a comment on an ad, nothing catches it: not the webhook path, not the safety net.
Measured on one boosted post, comparing the comments the Graph API reports against what actually arrived:
Link 🎉🎉🎉LinkLinkLinkLink?LinkThree of six keyword comments were lost. Two were recent enough to re-enqueue by hand; the third was past the 7-day private-reply window and is gone. Boosting is also when comment volume is highest, so the safety net was missing exactly when it was needed most.
This is the same shape as #25 (which fixed the matching of ad comments) — the missing half is the polling path.
The fix
adMediaFor(postId)returns the ad copies of a post, and the sweep checks those media alongside the post itself.The ad ids come from the webhooks already stored, not from the ads API — reading the ads API would require
ads_managementon top of the permissions the app asks for today, which seemed a heavy price for a safety net. The trade-off is explicit in the code: an ad becomes visible to the sweep only after one comment on it has arrived. That is enough for this failure mode, where some webhooks arrive and others do not.Failure is contained: a lookup error returns an empty list and the sweep still checks the post exactly as before. A campaign on a post that was never boosted issues one extra query and gets nothing back.
Verification
npm run typecheck,npm run lint,npm test,npm run buildall pass — 168 tests, 6 of them new.The query was also run against a live database, where it correctly returned the ad media id for the boosted post. The five unit tests cover: the ad is returned, the post itself never is (it would be swept twice), rows without an id are dropped, an unboosted post yields nothing, and a failed query returns empty.