fix(whatsapp): truncate location fallback_title to 255 chars to avoid silent message drop (#14517)
## Summary
`Whatsapp::IncomingMessageBaseService#attach_location` builds a
`fallback_title` by concatenating `location['name']` and
`location['address']` with no length cap, then stores it directly into
`Attachment#fallback_title`. `ApplicationRecord` enforces a generic
255-character limit on string columns, so any WhatsApp location whose
`"#{name}, #{address}"` exceeds 255 chars (a common case for Google
Places that include a long full address) raises
`ActiveRecord::RecordInvalid` deep inside the Sidekiq job. The message
and attachment INSERTs are part of the same transaction, so the whole
thing rolls back. Sidekiq retries once; the retry dedup-skips the wamid
silently and exits without an error. **Result: the message is
irrecoverably lost — no row in `messages`, no entry in the UI, no
outgoing webhook, no clue for the operator.**
Confirmed in `v4.13.0`, `v4.14.0`, and `develop` (commit `f33e469`,
2026-05-20). No upstream issue found before opening this PR.
## How to reproduce
1. From WhatsApp, share a Google Place whose `name + ", " + address` is
> 255 chars. The Spanish business address `Gremi de Fusters, 33,
Edificio VIP Asima, Piso 2, Local 2, Norte, 07009 Polígon industrial de
Son Castelló, Illes Balears, España` (132 chars) used as both `name` and
`address` is enough.
2. Sidekiq logs:
```
ERROR ActiveRecord::RecordInvalid: Validation failed:
Attachments fallback title is too long (maximum is 255 characters)
```
3. The `messages` table has no row. The conversation UI shows nothing
for that timestamp.
4. The first retry "Performed" successfully but creates nothing — the
dedup-by-source-id silently swallows the failure.
## Fix
Cap the existing concatenated title at 255 chars via `.first(255)`.
Minimal change, no behavioural difference for any message shorter than
the limit, prevents the silent data loss for any longer ones.
```diff
- location_name = location['name'] ? "#{location['name']}, #{location['address']}" : ''
+ location_name = (location['name'] ? "#{location['name']}, #{location['address']}" : '').first(255)
```
## Alternatives considered
- **Increase the validation limit on `Attachment#fallback_title`**: more
invasive; would touch other inbound channels and possibly require a DB
column change.
- **Use `name` alone (no concat)**: cleaner semantically (in many real
payloads `name == address`), but changes user-visible behaviour. Left as
a follow-up if desired.
- **Truncate with ellipsis**: cosmetic only; deferred.
This PR is intentionally minimal so it can be merged on its own.
---------
Co-authored-by: Sony Mathew <sony@chatwoot.com>
Co-authored-by: Sony Mathew <2040199+sony-mathew@users.noreply.github.com>
This commit is contained in:
co-authored by
Sony Mathew
Sony Mathew
parent
b791d75b30
commit
7acbe8b3ff
@@ -147,7 +147,7 @@ class Whatsapp::IncomingMessageBaseService
|
||||
|
||||
def attach_location
|
||||
location = messages_data.first['location']
|
||||
location_name = location['name'] ? "#{location['name']}, #{location['address']}" : ''
|
||||
location_name = (location['name'] ? "#{location['name']}, #{location['address']}" : '').first(255)
|
||||
@message.attachments.new(
|
||||
account_id: @message.account_id,
|
||||
file_type: file_content_type(message_type),
|
||||
|
||||
@@ -381,6 +381,32 @@ describe Whatsapp::IncomingMessageService do
|
||||
expect(location_attachment.coordinates_long).to eq(-122.3895553)
|
||||
expect(location_attachment.external_url).to eq('http://location_url.test')
|
||||
end
|
||||
|
||||
it 'truncates long fallback titles to avoid dropping location messages' do
|
||||
long_place_name = [
|
||||
'Gremi de Fusters, 33, Edificio VIP Asima, Piso 2, Local 2, Norte',
|
||||
'07009 Poligon industrial de Son Castello, Illes Balears, Espana'
|
||||
].join(', ')
|
||||
source_id = 'wamid.long-location-fallback-title'
|
||||
params = {
|
||||
'contacts' => [{ 'profile' => { 'name' => 'Sojan Jose' }, 'wa_id' => '2423423243' }],
|
||||
'messages' => [{ 'from' => '2423423243', 'id' => source_id,
|
||||
'location' => { 'id' => 'b1c68f38-8734-4ad3-b4a1-ef0c10d683',
|
||||
:address => long_place_name,
|
||||
:latitude => 37.7893768,
|
||||
:longitude => -122.3895553,
|
||||
:name => long_place_name,
|
||||
:url => 'http://location_url.test' },
|
||||
'timestamp' => '1633034394', 'type' => 'location' }]
|
||||
}.with_indifferent_access
|
||||
|
||||
expect { described_class.new(inbox: whatsapp_channel.inbox, params: params).perform }
|
||||
.to change { Message.where(source_id: source_id).count }.from(0).to(1)
|
||||
|
||||
location_attachment = Message.find_by!(source_id: source_id).attachments.first
|
||||
expect(location_attachment.fallback_title).to eq("#{long_place_name}, #{long_place_name}".first(255))
|
||||
expect(location_attachment.fallback_title.length).to eq(255)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when valid contact message params' do
|
||||
|
||||
Reference in New Issue
Block a user