BZ-2950: fix(breeze-buddy): suppress harmless Pipecat WS close error on client-initiated disconnect#759
Conversation
…on client-initiated disconnect
WalkthroughThis 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. ChangesPipecat WebSocket Close Error Log Suppression
🎯 2 (Simple) | ⏱️ ~10 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Comment |
There was a problem hiding this comment.
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. |
| 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. |
| # Suppress known-harmless Pipecat WS close error on client-initiated disconnect. | ||
| # Must be called before any pipeline runs. | ||
| install_pipecat_log_filter() |
| 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 | ||
| ): |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
app/ai/voice/agents/breeze_buddy/utils/pipecat_log_filter.py (2)
41-44: ⚡ Quick winAdd type annotations to module-level constants.
The constants should be explicitly annotated as
strfor 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 valueConsider 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 inapp/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()orlog_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
📒 Files selected for processing (3)
app/ai/voice/agents/breeze_buddy/utils/pipecat_log_filter.pyapp/core/logger/__init__.pyapp/main.py
| 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 | ||
| ): |
There was a problem hiding this comment.
🛠️ 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.
Summary
RuntimeError: Cannot call "send" once a close message has been senterror that Pipecat logs at ERROR level during pipeline teardown when the remote client has already closed the WebSocket firstapp/ai/voice/agents/breeze_buddy/utils/pipecat_log_filter.pydocumenting the Pipecat transport race and exportinginstall_pipecat_log_filter()as the startup sentinelfilter_spam_logs()inapp/core/logger/__init__.py(ensuring the record is dropped from all configured sinks, not just an additional one)install_pipecat_log_filter()at the top of the FastAPI lifespan inapp/main.py, before any pipeline runsWhy this is safe to suppress
Pipecat's
FastAPIWebsocketClient.disconnect()callsws.close()duringEndFrameteardown. Both the happy-path call ending and client-initiated disconnect converge on the same teardown sequence. When the client closes first, Starlette raises aRuntimeErrorwhich 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.pyFiles Changed
app/ai/voice/agents/breeze_buddy/utils/pipecat_log_filter.py(created)app/core/logger/__init__.py(modified — added pipecat WS close check tofilter_spam_logs)app/main.py(modified — import + callinstall_pipecat_log_filter()in lifespan)Discussion
Original thread: https://slack.com/archives/C09ST3HSDT6/p1778580147065979
Summary by CodeRabbit