From 6956cd54150ed5c53cf2759ddfe01fff22c52fbb Mon Sep 17 00:00:00 2001 From: MarekWo Date: Wed, 25 Feb 2026 21:34:53 +0100 Subject: [PATCH] fix(dm): Skip retry for contacts without path (flood sends) When a contact has no path set (out_path_len == -1), the initial msg is already sent as flood. Retrying would spam the network with up to 8 flood messages in quick succession. Now checks contact's out_path_len via .ci command before starting the retry loop. If no path exists, logs and skips retry entirely. Co-Authored-By: Claude Opus 4.6 --- meshcore-bridge/bridge.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/meshcore-bridge/bridge.py b/meshcore-bridge/bridge.py index bf01c01..70ef2fa 100644 --- a/meshcore-bridge/bridge.py +++ b/meshcore-bridge/bridge.py @@ -854,12 +854,45 @@ class MeshCLISession: f"flood={self.auto_retry_max_flood}, " f"timeout={suggested_timeout}ms") + def _get_contact_path_len(self, recipient): + """Check contact's out_path_len via .ci command. Returns -1 if no path/unknown.""" + try: + result = self.execute_command(['.ci', recipient], timeout=5) + if result.get('success'): + stdout = result.get('stdout', '').strip() + if stdout: + # .ci returns full contact JSON, may have prompt echo before it + # Try to find JSON object in output + for start_idx in range(len(stdout)): + if stdout[start_idx] == '{': + try: + parsed = json.loads(stdout[start_idx:]) + if isinstance(parsed, dict): + return parsed.get('out_path_len', -1) + except json.JSONDecodeError: + continue + except Exception as e: + logger.warning(f"Retry: could not check path for {recipient}: {e}") + return -1 + def _retry_send(self, recipient, text, original_ack, suggested_timeout, cancel_event): """Background retry loop for a DM message. Phase 1: Direct attempts (up to auto_retry_max_attempts) Phase 2: Flood attempts (up to auto_retry_max_flood) after reset_path + + If the contact has no path (out_path_len == -1), the initial send was + already a flood. Skip retry to avoid spamming the network. """ + # Check if contact has a path - if not, skip retry (initial send was flood) + path_len = self._get_contact_path_len(recipient) + if path_len == -1: + logger.info(f"Retry: skipping for '{recipient}' - no path set " + f"(initial send was flood), ack={original_ack}") + with self.retry_lock: + self.active_retries.pop(original_ack, None) + return + # Wait timeout in seconds (use suggested_timeout from device, with 1.2x margin) wait_timeout = max(suggested_timeout / 1000 * 1.2, 5.0)