From 7acbe8b3ff154ceee410462cf25c213501747c27 Mon Sep 17 00:00:00 2001 From: JoseGrdar <168092473+JoseGrdar@users.noreply.github.com> Date: Wed, 3 Jun 2026 09:20:21 +0200 Subject: [PATCH] fix(whatsapp): truncate location fallback_title to 255 chars to avoid silent message drop (#14517) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 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 Co-authored-by: Sony Mathew <2040199+sony-mathew@users.noreply.github.com> --- .../whatsapp/incoming_message_base_service.rb | 2 +- .../whatsapp/incoming_message_service_spec.rb | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/app/services/whatsapp/incoming_message_base_service.rb b/app/services/whatsapp/incoming_message_base_service.rb index 722ac3e4d..9e0720f74 100644 --- a/app/services/whatsapp/incoming_message_base_service.rb +++ b/app/services/whatsapp/incoming_message_base_service.rb @@ -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), diff --git a/spec/services/whatsapp/incoming_message_service_spec.rb b/spec/services/whatsapp/incoming_message_service_spec.rb index fe6b179c1..430aa1561 100644 --- a/spec/services/whatsapp/incoming_message_service_spec.rb +++ b/spec/services/whatsapp/incoming_message_service_spec.rb @@ -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