iachat/app/jobs/conversations/resolution_job.rb
Tanmay Deep Sharma d451615811
fix: prevent NoMethodError in mute helpers when contact is nil (#13277)
## Linear Ticket

https://linear.app/chatwoot/issue/CW-4569/nomethoderror-undefined-method-blocked-for-nil-nomethoderror

## Description
Fixes NoMethodError in ConversationMuteHelpers that occurs during
contact deletion race condition.
When a contact is deleted, there's a brief window (~50-150ms) where
contact_id becomes nil but conversations still exist. If ResolutionJob
runs during this window, the muted? method crashes trying to call
blocked? on nil.Fixes # (issue)

## Type of change

- [ ] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?

- Created orphaned conversations (contact_id = nil)
- Called muted?, mute!, unmute! - all return gracefully
- Verified async deletion still works correctly

## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules



Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-01-15 22:00:09 -08:00

28 lines
1.2 KiB
Ruby

class Conversations::ResolutionJob < ApplicationJob
queue_as :low
def perform(account:)
# limiting the number of conversations to be resolved to avoid any performance issues
resolvable_conversations = conversation_scope(account).limit(Limits::BULK_ACTIONS_LIMIT)
resolvable_conversations.each do |conversation|
# send message from bot that conversation has been resolved
# do this is account.auto_resolve_message is set
::MessageTemplates::Template::AutoResolve.new(conversation: conversation).perform if account.auto_resolve_message.present?
conversation.add_labels(account.auto_resolve_label) if account.auto_resolve_label.present?
conversation.toggle_status
end
end
private
def conversation_scope(account)
base_scope = if account.auto_resolve_ignore_waiting
account.conversations.resolvable_not_waiting(account.auto_resolve_after)
else
account.conversations.resolvable_all(account.auto_resolve_after)
end
# Exclude orphan conversations where contact was deleted but conversation cleanup is pending
base_scope.where.not(contact_id: nil)
end
end