Skip to content

Make notification.deliver idempotent on retry - #1298

Open
johnpooch wants to merge 3 commits into
mainfrom
claude/diplicity-notifications-arch-j1qwzq
Open

Make notification.deliver idempotent on retry#1298
johnpooch wants to merge 3 commits into
mainfrom
claude/diplicity-notifications-arch-j1qwzq

Conversation

@johnpooch

@johnpooch johnpooch commented Aug 29, 2026

Copy link
Copy Markdown
Owner

What this PR does

notification.deliver is registered with retry=3 and re-read its batch by id with no status filter (service/notification/tasks.py:19), so any retry re-sent every delivery in the batch — including ones already marked sent. It now loads only PENDING rows, so a retry picks up exactly the deliveries that have not gone out yet.

It also drops deliveries that are too old to be worth sending: rows older than DELIVER_MAX_AGE_HOURS are marked with a new expired status instead of being sent. PUSH_TTL (service/notification/utils.py:11) caps how long FCM buffers a message for an offline device, but it is computed at send time, so it does not bound the enqueue-to-send lag.

Both filters are per-delivery, not per-batch: a batch that mixes sent and pending rows, or stale and fresh ones, still delivers the rows that qualify.

Closes #1281

Checklist

  • This PR does one thing — no unrelated fixes, refactors, or drive-by cleanups bundled in
  • For PRs of any significant complexity: I ran /review-pr against this PR in Claude Code and addressed (or responded to) its findings
  • Tests cover the change
  • Screenshots embedded in the PR description for any visual changes (see CLAUDE.md)

Four tests in service/notification/tests.py::TestNotificationDeliver cover the sent-skip, the partly-sent batch, the expiry, and the partly-stale batch. Full backend suite: 2309 passed, 8 skipped. No visual changes, so no screenshots. /review-pr has not been run against this branch.

deliver is registered with retry=3 and re-read its batch by id with no
status filter, so any retry re-sent every delivery in the batch, including
ones already marked sent. Load only PENDING rows so a retry picks up
exactly the deliveries that have not gone out yet.

Deliveries whose enqueue-to-send lag exceeds DELIVER_MAX_AGE_HOURS are
marked with a new expired status instead of being sent: PUSH_TTL bounds
how long FCM buffers a message for an offline device, but it is computed
at send time, so it does not bound the lag before the send.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTSUJGBPgDacmsi1kyi1ze
Comment thread service/notification/tasks.py Outdated
Comment thread service/notification/tasks.py Outdated
claude added 2 commits August 29, 2026 13:18
Modification logic belongs on the manager, so expire_stale moves out of
notification.tasks and onto NotificationDeliveryManager, taking the max
age from the caller.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTSUJGBPgDacmsi1kyi1ze
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FTSUJGBPgDacmsi1kyi1ze
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

notification.deliver re-sends deliveries that were already sent

2 participants