fix(db): handle ConnectionDoesNotExistError with retry + pool invalidation#154
Merged
ohld merged 6 commits intoproductionfrom Apr 1, 2026
Merged
fix(db): handle ConnectionDoesNotExistError with retry + pool invalidation#154ohld merged 6 commits intoproductionfrom
ohld merged 6 commits intoproductionfrom
Conversation
…ation pool_pre_ping=True catches most stale connections but there's a race: Postgres can close the connection between the ping and the actual query. When that happens asyncpg raises ConnectionDoesNotExistError. Two-layer fix: - handle_error event listener: marks ConnectionDoesNotExistError as a pool disconnect so SQLAlchemy invalidates that connection and never returns it again - Single retry in fetch_one / fetch_all / execute: if the race condition fires, the retry acquires a fresh connection from the pool and succeeds transparently Fixes Sentry issues 7347427630 (fetch_all) and 7379710797 (webhook handler). Co-Authored-By: Paperclip <noreply@paperclip.ing>
Two files had lines exceeding the 100-char limit (introduced in prior commits, not by the db stale-connection fix). Fixed to unblock CI on PR #154. Co-Authored-By: Paperclip <noreply@paperclip.ing>
Two separate bugs both caused empty text to be sent to Telegram:
1. wrapped.py: stats_report stored as "" (from None or "") meant
uw.get("stats_report", "📊") returned "" instead of the fallback.
Fix: use `or` instead of dict default so empty string also falls back.
2. feedback.py: handle_feedback_reply had no guard for non-text admin
replies (sticker, voice, etc.). text=None would reach send_message.
Fix: return early if reply_text is falsy.
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Pre-existing formatting issue on this branch — production side already formatted via a prior merge. Fixing to unblock CI lint check on PR #154. Co-Authored-By: Paperclip <noreply@paperclip.ing>
Co-Authored-By: Paperclip <noreply@paperclip.ing>
Production added txt extraction variable for stats_report (fix(wrapped) commit). Resolved by accepting production's version. Co-Authored-By: Paperclip <noreply@paperclip.ing>
Member
Author
|
Staff Engineer Review — APPROVED Code review complete. Two-layer fix is correct:
On write retry safety: Pre-existing lint + conflicts fixed: 3 E501 violations in unrelated files, ruff formatting in Minor informational (non-blocking):
Merging. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
pool_pre_ping=Truecatches most stale connections, but there's a race: Postgres can close a connection between the ping and the actual queryConnectionDoesNotExistError, which was surfacing as unhandledDBAPIErrorinfetch_all(Sentry 7347427630) and the webhook handler (Sentry 7379710797)Two-layer fix in
src/database.py:handle_errorevent listener marksConnectionDoesNotExistErroras a pool disconnect — SQLAlchemy invalidates that connection and never returns it from the pool againfetch_one/fetch_all/execute— if the race fires, the retry acquires a fresh healthy connection and the request succeeds transparentlyTest plan
ConnectionDoesNotExistErrorafter deploydocker ps+docker logshealth check per CLAUDE.md checklist🤖 Generated with Claude Code