From 4b2bbd38e77130e8c7272c8b25d7cbd5bafb2576 Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Wed, 20 May 2026 16:41:48 +0530 Subject: [PATCH] fix(voice): re-check call state under lock before forcing agent_rejected --- .../api/v1/accounts/conference_controller.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/enterprise/app/controllers/api/v1/accounts/conference_controller.rb b/enterprise/app/controllers/api/v1/accounts/conference_controller.rb index 921122498..0bea29843 100644 --- a/enterprise/app/controllers/api/v1/accounts/conference_controller.rb +++ b/enterprise/app/controllers/api/v1/accounts/conference_controller.rb @@ -28,9 +28,7 @@ class Api::V1::Accounts::ConferenceController < Api::V1::Accounts::BaseControlle def destroy call = resolve_call! rejecting = agent_rejecting_before_pickup?(call) - # Tear down the provider conference before marking the call terminal: if - # end_conference raises, the call stays ringing (non-terminal) and remains - # repairable by later status webhooks instead of being stuck as failed. + # Tear down provider side first so a teardown failure leaves the call repairable. Voice::Provider::Twilio::ConferenceService.new(call: call).end_conference finalize_as_agent_reject!(call) if rejecting render json: { status: 'success', id: call.conversation.display_id } @@ -71,11 +69,14 @@ class Api::V1::Accounts::ConferenceController < Api::V1::Accounts::BaseControlle end def finalize_as_agent_reject!(call) - call.update!( - status: 'failed', - end_reason: 'agent_rejected', - accepted_by_agent_id: Current.user.id - ) - Voice::CallMessageBuilder.new(call).update_status!(status: 'failed', agent: Current.user) + # Re-check under a row lock: a webhook may have accepted/completed the call + # while end_conference was in flight, so don't force agent_rejected on stale state. + rejected = call.with_lock do + next false unless agent_rejecting_before_pickup?(call) + + call.update!(status: 'failed', end_reason: 'agent_rejected', accepted_by_agent_id: Current.user.id) + true + end + Voice::CallMessageBuilder.new(call).update_status!(status: 'failed', agent: Current.user) if rejected end end