From bac2841298be76c3399c017e7324f0631d61609e Mon Sep 17 00:00:00 2001 From: Mark Veidemanis Date: Thu, 19 Feb 2026 03:21:01 +0000 Subject: [PATCH] fix(whatsapp): guard get_me() calls to enable QR pairing on fresh sessions --- core/clients/whatsapp.py | 51 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/core/clients/whatsapp.py b/core/clients/whatsapp.py index f169c21..57e3278 100644 --- a/core/clients/whatsapp.py +++ b/core/clients/whatsapp.py @@ -159,6 +159,10 @@ class WhatsAppClient(ClientBase): db_dir = os.path.dirname(self.session_db) if db_dir: os.makedirs(db_dir, exist_ok=True) + if db_dir and not os.access(db_dir, os.W_OK): + raise PermissionError( + f"session db directory is not writable: {db_dir}" + ) except Exception as exc: self._publish_state( connected=False, @@ -179,6 +183,19 @@ class WhatsAppClient(ClientBase): ) return + if not self._session_has_device(): + self._publish_state( + connected=False, + warning="No linked WhatsApp device. Waiting for QR pairing.", + accounts=[], + last_event="no_linked_device", + pair_status="needs_pairing", + ) + self.log.info( + "whatsapp session db has no linked device — connect will " + "start QR pairing flow" + ) + self._register_event_handlers(wa_events) try: @@ -457,6 +474,32 @@ class WhatsAppClient(ClientBase): ) return None + def _session_has_device(self): + """Check whatsmeow_device table for a linked device row. + + Neonize's Go layer panics (SIGSEGV in GetMe) when connect() is called + on a session with no linked device. Querying the SQLite schema directly + lets us skip connect() and avoid crashing the entire UR process. + """ + if not os.path.exists(self.session_db): + return False + try: + conn = sqlite3.connect(self.session_db) + cursor = conn.cursor() + cursor.execute( + "SELECT COUNT(*) FROM sqlite_master " + "WHERE type='table' AND name='whatsmeow_device'" + ) + if cursor.fetchone()[0] == 0: + conn.close() + return False + cursor.execute("SELECT COUNT(*) FROM whatsmeow_device") + count = cursor.fetchone()[0] + conn.close() + return count > 0 + except Exception: + return False + def _register_event_handlers(self, wa_events): connected_ev = getattr(wa_events, "ConnectedEv", None) message_ev = getattr(wa_events, "MessageEv", None) @@ -1361,6 +1404,11 @@ class WhatsAppClient(ClientBase): return "" if not hasattr(self._client, "get_me"): return self.client_name + # Guard: Neonize Go GetMe() panics (SIGSEGV) when cli.LID is nil + # on unlinked sessions. Only safe to call after pairing completes + # and the device row exists in the whatsmeow_device table. + if not self._session_has_device(): + return self.client_name try: me = await self._maybe_await(self._client.get_me()) except Exception: @@ -1785,7 +1833,8 @@ class WhatsAppClient(ClientBase): self._connected = True self._publish_state(connected=True, warning="", pair_status="connected") return True - if hasattr(self._client, "get_me"): + # Guard: Neonize Go GetMe() panics (SIGSEGV) on unlinked sessions. + if hasattr(self._client, "get_me") and self._session_has_device(): try: me = await self._maybe_await(self._client.get_me()) if me: