Skip to content

fix(breeze_buddy): guard handle_call_completion against duplicate finished-lead callbacks - #938

Open
Tara-ag wants to merge 2 commits into
releasefrom
bugfix/lead-tracker-concurrency-guard
Open

fix(breeze_buddy): guard handle_call_completion against duplicate finished-lead callbacks#938
Tara-ag wants to merge 2 commits into
releasefrom
bugfix/lead-tracker-concurrency-guard

Conversation

@Tara-ag

@Tara-ag Tara-ag commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds a status guard in handle_call_completion (app/ai/voice/agents/breeze_buddy/managers/calls.py), placed immediately after the lead lookup. If lead.status == LeadCallStatus.FINISHED, the function logs an info message and returns the lead immediately, short-circuiting before:

  • redundant DB updates
  • redundant outbound number releases
  • duplicate retry scheduling

This resolves DB row-locking and contention issues seen during concurrent call completion callbacks for the same lead (racing webhooks/callbacks arriving independently for a lead that has already been marked FINISHED).

Files changed

  • app/ai/voice/agents/breeze_buddy/managers/calls.py — added early-return guard in handle_call_completion

Verification

  • uv run black --check . — pass
  • uv run isort . --profile black --check-only — pass
  • uv run pyrefly check — pass (no new errors introduced)

Note for reviewer

This branch currently has 2 commits ahead of release: an empty auto-generated chore: start tara task ... commit created when the branch was provisioned, and this fix commit. Per repo convention, PRs must contain exactly 1 commit (enforced in CI) — squashing would require a force-push, which is outside this automation's permitted actions. Please squash-merge, or let me know if you'd like the branch squashed before merge.

Discussion

Original Slack thread: https://slack.com/archives/C09ST3HSDT6/p1784636128369249

Summary by CodeRabbit

  • Bug Fixes
    • Prevented duplicate call-completion processing when multiple completion events occur simultaneously.
    • Avoided redundant updates, number releases, and retry scheduling for already completed calls.

…dy-finished leads

Adds a status guard in handle_call_completion right after the lead lookup so
that concurrent completion callbacks for the same lead short-circuit once the
lead is already FINISHED, avoiding redundant DB updates, redundant outbound
number releases, and duplicate retry scheduling under DB row-locking
contention.
Copilot AI review requested due to automatic review settings July 21, 2026 12:38
@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds a concurrency guard to call completion handling so callbacks for already finished leads exit before repeating updates, number releases, or retry scheduling.

Changes

Call completion handling

Layer / File(s) Summary
Finished-state completion guard
app/ai/voice/agents/breeze_buddy/managers/calls.py
handle_call_completion logs and returns early when the lead is already in FINISHED state.

Estimated code review effort: 2 (Simple) | ~5 minutes

Suggested reviewers: swaroopvarma2359

Poem

A rabbit saw callbacks race,
And tucked a guard in place.
“Finished,” it said, “no need to run,”
No duplicate work, no extra fuss—
Back to the burrow, job well done!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding a guard to handle duplicate finished-lead callbacks in handle_call_completion.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bugfix/lead-tracker-concurrency-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/ai/voice/agents/breeze_buddy/managers/calls.py`:
- Around line 501-509: Move the lead.status == LeadCallStatus.FINISHED guard in
the completion callback below the outbound number release block, ensuring
_release_number executes before any early return. Preserve the guard’s
duplicate-completion behavior and logging after the release so channel tokens
are always reclaimed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: bae7c709-becb-48bc-a5a5-2e986ee17bc1

📥 Commits

Reviewing files that changed from the base of the PR and between c96a03c and f3091a5.

📒 Files selected for processing (1)
  • app/ai/voice/agents/breeze_buddy/managers/calls.py

Comment on lines +501 to +509
# Guard: if a concurrent callback already finished this lead, skip to avoid
# redundant DB updates, redundant outbound number releases, and duplicate
# retry scheduling caused by racing completion callbacks for the same lead.
if lead.status == LeadCallStatus.FINISHED:
logger.info(
f"Lead {lead.id} is already FINISHED for call_id: {call_id}, skipping duplicate completion callback."
)
return lead

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Relocate the FINISHED guard below the outbound number release to prevent channel token leaks.

Although the PR description explicitly mentions preventing "redundant outbound number releases," this contradicts the established resource-management pattern documented in the adjacent handle_unanswered_calls function (lines 615-617).

If a duplicate call was actually initiated, it acquired its own channel token. Returning early before releasing the number will leak that token. As noted in the existing comments, _release_number is idempotent, and releasing tokens is safe because over-counts are trimmed by the periodic reconciliation, whereas skipping the release results in a leaked channel allocation.

Consider moving this guard immediately after the outbound number release block.

🔄 Proposed relocation
-    # Guard: if a concurrent callback already finished this lead, skip to avoid
-    # redundant DB updates, redundant outbound number releases, and duplicate
-    # retry scheduling caused by racing completion callbacks for the same lead.
-    if lead.status == LeadCallStatus.FINISHED:
-        logger.info(
-            f"Lead {lead.id} is already FINISHED for call_id: {call_id}, skipping duplicate completion callback."
-        )
-        return lead
-
     # Always release outbound number (including transfers — bot leaves, cleanup happens here)
     if lead.outbound_number_id:
         outbound_number = await get_outbound_number_by_id(lead.outbound_number_id)
         if outbound_number:
             await _release_number(outbound_number.id, outbound_number.provider)
             # Event-driven dispatch: return a token to the channel semaphore.
             # Idempotent in aggregate — reconcile_channel_tokens trims any
             # over-count caused by duplicate webhooks within 60s.
             await release_channel_token(outbound_number.id)
         else:
             logger.error(
                 f"Could not find outbound number with id: {lead.outbound_number_id} to release."
             )
     else:
         logger.info(f"No outbound number id for lead: {lead.id}")
+
+    # Guard: if a concurrent callback already finished this lead, skip to avoid
+    # redundant DB updates and duplicate retry scheduling caused by racing 
+    # completion callbacks for the same lead.
+    if lead.status == LeadCallStatus.FINISHED:
+        logger.info(
+            f"Lead {lead.id} is already FINISHED for call_id: {call_id}, skipping duplicate completion callback."
+        )
+        return lead
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/ai/voice/agents/breeze_buddy/managers/calls.py` around lines 501 - 509,
Move the lead.status == LeadCallStatus.FINISHED guard in the completion callback
below the outbound number release block, ensuring _release_number executes
before any early return. Preserve the guard’s duplicate-completion behavior and
logging after the release so channel tokens are always reclaimed.

Copilot AI 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.

Pull request overview

This PR aims to make Breeze Buddy’s telephony completion path (handle_call_completion) idempotent when multiple “call finished” callbacks race for the same lead, by short-circuiting when the lead is already FINISHED to reduce DB contention and duplicate side effects.

Changes:

  • Add an early-return guard in handle_call_completion when lead.status == LeadCallStatus.FINISHED.
  • Log and return the already-finished lead to avoid duplicate completion processing.

Comment on lines +504 to +508
if lead.status == LeadCallStatus.FINISHED:
logger.info(
f"Lead {lead.id} is already FINISHED for call_id: {call_id}, skipping duplicate completion callback."
)
return lead

@Tara-ag Tara-ag left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Review Summary

Files reviewed: 1 (app/ai/voice/agents/breeze_buddy/managers/calls.py)

New issues raised this run: 0

I inspected the single-file diff and the existing review threads. The concurrency guard itself is a reasonable fix for duplicate completion callbacks, but the placement before the outbound-number/channel release is already flagged by two existing inline comments (CodeRabbit and Copilot). I concur with that feedback: handle_unanswered_calls in the same file releases the outbound number before its FINISHED guard, and the release helpers are idempotent, so moving the guard below the release block avoids leaking channel tokens when a duplicate callback arrives after the lead is already marked FINISHED.

No additional blocking issues (hardcoded secrets, SQL injection, auth/authorization flaws, SSRF, PII exposure, or migration edits) were found in this change. The existing feedback should be addressed before merge.

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.

3 participants