Skip to content

BZ-2950: fix(breeze-buddy): suppress harmless Pipecat WS close error on client-initiated disconnect#759

Open
titan-juspay wants to merge 1 commit into
releasefrom
BZ-2950-suppress-pipecat-ws-close-error-on-disconnect
Open

BZ-2950: fix(breeze-buddy): suppress harmless Pipecat WS close error on client-initiated disconnect#759
titan-juspay wants to merge 1 commit into
releasefrom
BZ-2950-suppress-pipecat-ws-close-error-on-disconnect

Conversation

@titan-juspay
Copy link
Copy Markdown
Collaborator

@titan-juspay titan-juspay commented May 12, 2026

Summary

  • Suppresses the known-harmless RuntimeError: Cannot call "send" once a close message has been sent error that Pipecat logs at ERROR level during pipeline teardown when the remote client has already closed the WebSocket first
  • Creates app/ai/voice/agents/breeze_buddy/utils/pipecat_log_filter.py documenting the Pipecat transport race and exporting install_pipecat_log_filter() as the startup sentinel
  • Adds the suppression check to the existing filter_spam_logs() in app/core/logger/__init__.py (ensuring the record is dropped from all configured sinks, not just an additional one)
  • Calls install_pipecat_log_filter() at the top of the FastAPI lifespan in app/main.py, before any pipeline runs

Why this is safe to suppress

Pipecat's FastAPIWebsocketClient.disconnect() calls ws.close() during EndFrame teardown. Both the happy-path call ending and client-initiated disconnect converge on the same teardown sequence. When the client closes first, Starlette raises a RuntimeError which Pipecat catches and logs at ERROR — but the call flow, data integrity, and cleanup are unaffected. This error has zero operational impact and triggers false monitoring alerts.

Reference: pipecat v1.1.0 src/pipecat/transports/websocket/fastapi.py

Files Changed

  • app/ai/voice/agents/breeze_buddy/utils/pipecat_log_filter.py (created)
  • app/core/logger/__init__.py (modified — added pipecat WS close check to filter_spam_logs)
  • app/main.py (modified — import + call install_pipecat_log_filter() in lifespan)

Discussion

Original thread: https://slack.com/archives/C09ST3HSDT6/p1778580147065979

Summary by CodeRabbit

  • Bug Fixes
    • Suppressed harmless WebSocket close errors from voice agent operations. Known benign errors occurring during client disconnections are now automatically filtered during startup, reducing unnecessary error logs while preserving proper error detection for genuine issues.

Review Change Stack

Copilot AI review requested due to automatic review settings May 12, 2026 12:50
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

Walkthrough

This PR adds log filtering to suppress a known WebSocket close race error from Pipecat transport logs. It introduces a new utility module defining error-matching patterns, integrates them into the logger's spam filter, and activates the filter during FastAPI startup to prevent noisy ERROR logs on client disconnects.

Changes

Pipecat WebSocket Close Error Log Suppression

Layer / File(s) Summary
Pipecat log filter utility module
app/ai/voice/agents/breeze_buddy/utils/pipecat_log_filter.py
New module defining PIPECAT_WS_CLOSE_MARKER and STARLETTE_WS_CLOSE_ERROR string constants to match the WebSocket close error, plus idempotent install_pipecat_log_filter() function with module-level sentinel and DEBUG log on first activation.
Logger filter integration
app/core/logger/__init__.py
filter_spam_logs() adds an early-return condition matching both Pipecat loggers and the distinctive WebSocket close error substrings to block and suppress those records across all configured sinks.
Application startup wiring
app/main.py
Imports install_pipecat_log_filter and calls it at the beginning of the FastAPI lifespan startup context manager, before other initialization steps.

🎯 2 (Simple) | ⏱️ ~10 minutes

A filter springs to life, so clean and bright,
Suppressing whispers of WebSocket's plight,
Pipecat's close dance, no more to fright—
The logs now quiet through the app's night. 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: suppressing a harmless Pipecat WebSocket close error on client disconnect, which aligns with all three file modifications in the changeset.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 BZ-2950-suppress-pipecat-ws-close-error-on-disconnect

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 and usage tips.

Copy link
Copy Markdown

Copilot AI left a comment

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 reduces false ERROR alerts during Breeze Buddy call teardown by filtering out a known-harmless Pipecat WebSocket close race when the client disconnects first.

Changes:

  • Added a Pipecat WS-close suppression check to the global Loguru sink filter (filter_spam_logs) so the record is dropped across all sinks.
  • Introduced a Breeze Buddy utility module documenting the Pipecat transport race and exposing an install_pipecat_log_filter() startup hook.
  • Invoked the startup hook early in the FastAPI lifespan before any pipelines run.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
app/main.py Calls the Pipecat log-filter “install” hook during app startup.
app/core/logger/init.py Drops the specific Pipecat WS-close race log record in filter_spam_logs.
app/ai/voice/agents/breeze_buddy/utils/pipecat_log_filter.py Documents the race and defines constants + an install sentinel.

Comment on lines +15 to +19
The application's core logger (app/core/logger/__init__.py) integrates a check
in filter_spam_logs() that drops this specific record from all sinks.

This module provides the install sentinel and the string constants used by that
integration.
Comment thread app/main.py
Comment on lines +104 to +106
# Suppress known-harmless Pipecat WS close error on client-initiated disconnect.
# Must be called before any pipeline runs.
install_pipecat_log_filter()
Comment on lines +119 to +123
if (
logger_name.startswith("pipecat")
and "exception while closing the websocket" in message
and 'Cannot call "send" once a close message has been sent' in message
):
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
app/ai/voice/agents/breeze_buddy/utils/pipecat_log_filter.py (2)

41-44: ⚡ Quick win

Add type annotations to module-level constants.

The constants should be explicitly annotated as str for better type safety and IDE support.

📝 Add type annotations
 # The exact substrings Pipecat logs in FastAPIWebsocketClient.disconnect()
 # Source: pipecat v1.1.0 src/pipecat/transports/websocket/fastapi.py
-PIPECAT_WS_CLOSE_MARKER = "exception while closing the websocket"
-STARLETTE_WS_CLOSE_ERROR = 'Cannot call "send" once a close message has been sent'
+PIPECAT_WS_CLOSE_MARKER: str = "exception while closing the websocket"
+STARLETTE_WS_CLOSE_ERROR: str = 'Cannot call "send" once a close message has been sent'

As per coding guidelines: "Add type hints on all function signatures" and consistent annotation improves maintainability.

🤖 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/utils/pipecat_log_filter.py` around lines 41
- 44, Annotate the two module-level constants with explicit types: add a str
type annotation for PIPECAT_WS_CLOSE_MARKER and for STARLETTE_WS_CLOSE_ERROR so
they are declared as str constants (e.g., PIPECAT_WS_CLOSE_MARKER: str and
STARLETTE_WS_CLOSE_ERROR: str) while preserving their existing string values to
improve type safety and IDE support; locate these identifiers in
pipecat_log_filter.py and update their declarations accordingly.

50-67: 💤 Low value

Consider clarifying the function name or documentation.

The function name install_pipecat_log_filter() suggests it installs or configures a filter, but it only sets a module-level flag and logs a debug message. The actual filtering logic is always active in app/core/logger/__init__.py:filter_spam_logs() regardless of whether this function is called.

While the docstring mentions this is a "sentinel," the function name could be clearer (e.g., mark_pipecat_log_filter_active() or log_pipecat_filter_status()), or the documentation could be enhanced to emphasize its purely documentary role.

This is a minor naming/documentation clarity issue that doesn't affect functionality.

🤖 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/utils/pipecat_log_filter.py` around lines 50
- 67, The function install_pipecat_log_filter() only flips the module-level
sentinel _FILTER_INSTALLED and logs a debug line while the real filtering is
implemented in filter_spam_logs(); rename the function to something clearer
(e.g., mark_pipecat_log_filter_active or log_pipecat_filter_status) or broaden
the docstring to explicitly state it is purely a sentinel/logging helper and
does not register filtering logic, and update any references to the function
name accordingly so callers and future readers understand _FILTER_INSTALLED’s
role relative to app/core/logger/__init__.py::filter_spam_logs.
🤖 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/core/logger/__init__.py`:
- Around line 119-123: Replace the hardcoded substrings in the websocket-close
filter with the shared constants from pipecat_log_filter.py: add an import for
PIPECAT_WS_CLOSE_MARKER and STARLETTE_WS_CLOSE_ERROR at the top of
app/core/logger/__init__.py, then update the conditional inside the if block to
use PIPECAT_WS_CLOSE_MARKER and STARLETTE_WS_CLOSE_ERROR instead of the literal
strings (keeping the existing logger_name.startswith("pipecat") check); this
centralizes the message definitions and prevents duplication/drift.

---

Nitpick comments:
In `@app/ai/voice/agents/breeze_buddy/utils/pipecat_log_filter.py`:
- Around line 41-44: Annotate the two module-level constants with explicit
types: add a str type annotation for PIPECAT_WS_CLOSE_MARKER and for
STARLETTE_WS_CLOSE_ERROR so they are declared as str constants (e.g.,
PIPECAT_WS_CLOSE_MARKER: str and STARLETTE_WS_CLOSE_ERROR: str) while preserving
their existing string values to improve type safety and IDE support; locate
these identifiers in pipecat_log_filter.py and update their declarations
accordingly.
- Around line 50-67: The function install_pipecat_log_filter() only flips the
module-level sentinel _FILTER_INSTALLED and logs a debug line while the real
filtering is implemented in filter_spam_logs(); rename the function to something
clearer (e.g., mark_pipecat_log_filter_active or log_pipecat_filter_status) or
broaden the docstring to explicitly state it is purely a sentinel/logging helper
and does not register filtering logic, and update any references to the function
name accordingly so callers and future readers understand _FILTER_INSTALLED’s
role relative to app/core/logger/__init__.py::filter_spam_logs.
🪄 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: fe48f638-f53f-4afa-bc74-40dfb2298ad9

📥 Commits

Reviewing files that changed from the base of the PR and between 108e2fa and 8e9eebd.

📒 Files selected for processing (3)
  • app/ai/voice/agents/breeze_buddy/utils/pipecat_log_filter.py
  • app/core/logger/__init__.py
  • app/main.py

Comment on lines +119 to +123
if (
logger_name.startswith("pipecat")
and "exception while closing the websocket" in message
and 'Cannot call "send" once a close message has been sent' in message
):
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Import and use constants from pipecat_log_filter.py to avoid string duplication.

The error message substrings are hardcoded here, but pipecat_log_filter.py defines PIPECAT_WS_CLOSE_MARKER and STARLETTE_WS_CLOSE_ERROR constants with identical values. Importing these constants ensures a single source of truth and prevents drift if the error messages need updating.

♻️ Refactor to use shared constants

At the top of the file, add the import:

 from app.core.config.static import ENVIRONMENT, PROD_LOG_LEVEL
 from app.core.logger.context import get_log_context
+from app.ai.voice.agents.breeze_buddy.utils.pipecat_log_filter import (
+    PIPECAT_WS_CLOSE_MARKER,
+    STARLETTE_WS_CLOSE_ERROR,
+)

Then update the filter condition:

         if (
             logger_name.startswith("pipecat")
-            and "exception while closing the websocket" in message
-            and 'Cannot call "send" once a close message has been sent' in message
+            and PIPECAT_WS_CLOSE_MARKER in message
+            and STARLETTE_WS_CLOSE_ERROR in message
         ):
             return False
🤖 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/core/logger/__init__.py` around lines 119 - 123, Replace the hardcoded
substrings in the websocket-close filter with the shared constants from
pipecat_log_filter.py: add an import for PIPECAT_WS_CLOSE_MARKER and
STARLETTE_WS_CLOSE_ERROR at the top of app/core/logger/__init__.py, then update
the conditional inside the if block to use PIPECAT_WS_CLOSE_MARKER and
STARLETTE_WS_CLOSE_ERROR instead of the literal strings (keeping the existing
logger_name.startswith("pipecat") check); this centralizes the message
definitions and prevents duplication/drift.

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