Skip to content

Notify a thread's participants on every new comment, debounced - #204

Merged
HamptonMakes merged 2 commits into
mainfrom
hampton/slack-notifications-setup-f695c8
Aug 28, 2026
Merged

Notify a thread's participants on every new comment, debounced#204
HamptonMakes merged 2 commits into
mainfrom
hampton/slack-notifications-setup-f695c8

Conversation

@HamptonMakes

Copy link
Copy Markdown
Collaborator

Summary

  • Every comment now fires the comment_created notification event, not just the thread's first comment — replies now notify the thread's participants, not only the plan owner on thread creation.
  • SlackNotificationJob is debounced per-thread (2-minute window): a burst of comments (e.g. an agent posting several in a row) collapses into one Slack DM per recipient instead of one per comment.
  • Recipients = the plan owner + everyone who's commented in the thread, minus whoever posted in this batch — unless someone else in the batch said something new, in which case a same-batch commenter still hears about it.
  • A permanent per-recipient Slack failure (e.g. no Slack account for their email) no longer blocks notifying the rest of the batch; a transient error still retries the whole job.

Test plan

  • bundle exec rspec — full suite green (1805 examples)
  • bundle exec rubocop on touched files — clean
  • Manual smoke test in a running app: post a comment, then a quick follow-up reply, and confirm exactly one Slack DM lands per recipient after the debounce window

🤖 Generated with Claude Code

…ment

Every comment now fires the comment_created event, so a reply notifies
everyone already in the thread, not only the plan owner on the first
comment. SlackNotificationJob is debounced per-thread so a burst of
comments (e.g. an agent posting several in a row) collapses into one
Slack DM per recipient instead of one per comment, and a permanent
failure for one recipient no longer blocks the rest of the batch.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe18dd8484

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/jobs/slack_notification_job.rb Outdated
Comment thread app/jobs/slack_notification_job.rb Outdated
Comment on lines +15 to +18
pending_key = pending_key(comment_thread_id)
return if Rails.cache.read(pending_key)

Rails.cache.write(pending_key, true, expires_in: CACHE_EXPIRY)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Claim the debounce key atomically

When notification workers process two comments from the same thread concurrently, both can read a missing pending key before either writes it, so both enqueue delayed jobs. Those jobs later select the same batch and send duplicate DMs, defeating the debounce precisely for concurrent bursts. Use an atomic cache claim such as a write with unless_exist: true and enqueue only for the caller that acquired it.

Useful? React with 👍 / 👎.

Comment thread app/jobs/slack_notification_job.rb
Comment on lines +47 to +49
recipients.each do |user|
next unless user.email.present?
send_dm(user, text)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retry only recipients whose send failed

With multiple recipients, a transient Slack error after one or more successful sends raises out of this loop and retry_on reruns the whole job. Every recipient processed before the failure receives the same DM again on each retry, so a rate limit or temporary error for one user can spam the other participants. Preserve the failed recipient set or dispatch independently retryable sends per recipient.

Useful? React with 👍 / 👎.

- batch_start was Time.current at debounce-call time, which is always
  after the triggering comment's own created_at (the comment must
  exist to have enqueued the job that calls debounce). That silently
  excluded the triggering comment from its own notification. Now
  passed through from the comment's created_at instead.
- Debounce's pending-flag check-and-set was read-then-write, not
  atomic; two concurrent calls could both schedule a job. Use
  Rails.cache's unless_exist: true instead.
- The pending flag was cleared at the top of perform, before any work
  happened, so a comment arriving mid-retry could start an overlapping
  batch that clobbered this one's cached batch_start. Now only cleared
  after a full successful send.
- A transient error partway through the recipient loop caused the
  whole job to retry, re-sending to recipients who'd already
  succeeded. Track already-notified recipients in cache and skip them
  on retry.
- Align soft-delete scoping: recipients_for now uses .kept like the
  new_comments query, so a participant whose only comment was deleted
  doesn't stay a permanent participant asymmetrically.
@HamptonMakes
HamptonMakes merged commit cf2723c into main Aug 28, 2026
4 checks passed
@HamptonMakes
HamptonMakes deleted the hampton/slack-notifications-setup-f695c8 branch August 28, 2026 17:21
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.

1 participant