From 505b36b7afc6a746cfd9b4ee3f4b04b82354d12d Mon Sep 17 00:00:00 2001 From: Kevin Rajan <7121943+kvnloo@users.noreply.github.com> Date: Tue, 15 Sep 2026 03:06:36 -0500 Subject: [PATCH] fix(platforms): make QR fallback install tip target the active interpreter The Feishu, WeCom, QQ onboarding and Telegram managed-bot flows printed a hard-coded 'pip install qrcode' tip when the qrcode package was missing. In Hermes' isolated venv the bare pip either doesn't exist or targets an unrelated system Python. Print '{sys.executable} -m pip install qrcode' instead, matching the existing codebase convention for install hints. Fixes #111695 --- gateway/platforms/qqbot/onboard.py | 3 ++- hermes_cli/telegram_managed_bot.py | 2 +- plugins/platforms/feishu/adapter.py | 3 ++- plugins/platforms/wecom/adapter.py | 3 ++- tests/hermes_cli/test_telegram_managed_bot.py | 11 +++++++++++ 5 files changed, 18 insertions(+), 4 deletions(-) diff --git a/gateway/platforms/qqbot/onboard.py b/gateway/platforms/qqbot/onboard.py index 44a0b917b5..c804cf7fc3 100644 --- a/gateway/platforms/qqbot/onboard.py +++ b/gateway/platforms/qqbot/onboard.py @@ -4,6 +4,7 @@ from __future__ import annotations import logging +import sys import time from enum import IntEnum from typing import Optional, Tuple @@ -97,7 +98,7 @@ def qr_register(timeout_seconds: int = 600) -> Optional[dict]: print(f" Scan the QR code above, or open this URL directly:\n {url}") else: print(f" Open this URL in QQ on your phone:\n {url}") - print(" Tip: pip install qrcode to display a scannable QR code here") + print(f" Tip: {sys.executable} -m pip install qrcode to display a scannable QR code here") print() while time.monotonic() < deadline: try: diff --git a/hermes_cli/telegram_managed_bot.py b/hermes_cli/telegram_managed_bot.py index fa505c3de7..8b6d08bb65 100644 --- a/hermes_cli/telegram_managed_bot.py +++ b/hermes_cli/telegram_managed_bot.py @@ -76,7 +76,7 @@ def render_qr_terminal(url: str) -> str: def print_qr_code(url: str, *, include_link: bool = True) -> None: """Print a QR code to stdout, with URL fallback if qrcode is missing.""" - print(render_qr_terminal(url) or " (Install 'qrcode' for a scannable QR code: pip install qrcode)") + print(render_qr_terminal(url) or f" (Install 'qrcode' for a scannable QR code: {sys.executable} -m pip install qrcode)") if include_link: print(f" Link: {url}") diff --git a/plugins/platforms/feishu/adapter.py b/plugins/platforms/feishu/adapter.py index f63817630b..69c62c5a54 100644 --- a/plugins/platforms/feishu/adapter.py +++ b/plugins/platforms/feishu/adapter.py @@ -30,6 +30,7 @@ import logging import mimetypes import os import re +import sys import threading import time import uuid @@ -4103,7 +4104,7 @@ def _qr_register_inner(*, initial_domain: str, timeout_seconds: int) -> Optional print(f"\n Scan the QR code above, or open this URL directly:\n {qr_url}") else: print(f" Open this URL in Feishu / Lark on your phone:\n\n {qr_url}\n") - print(" Tip: pip install qrcode to display a scannable QR code here next time") + print(f" Tip: {sys.executable} -m pip install qrcode to display a scannable QR code here next time") print() result = _poll_registration( device_code=begin["device_code"], interval=begin["interval"], diff --git a/plugins/platforms/wecom/adapter.py b/plugins/platforms/wecom/adapter.py index be2cd63a04..9213740fc7 100644 --- a/plugins/platforms/wecom/adapter.py +++ b/plugins/platforms/wecom/adapter.py @@ -10,6 +10,7 @@ import asyncio import json import logging import re +import sys import time import uuid from datetime import datetime, timezone @@ -674,7 +675,7 @@ def qr_scan_for_bot_info(*, timeout_seconds: int = _QR_POLL_TIMEOUT) -> Optional print(f"\n Scan the QR code above, or open this URL directly:\n {page_url}") except Exception: print(f" Open this URL in WeCom on your phone:\n\n {page_url}\n") - print(" Tip: pip install qrcode to display a scannable QR code here next time") + print(f" Tip: {sys.executable} -m pip install qrcode to display a scannable QR code here next time") print("\n Fetching configuration results...", end="", flush=True) deadline = time.monotonic() + timeout_seconds query_url = f"{_QR_QUERY_URL}?scode={urllib.parse.quote(scode)}" diff --git a/tests/hermes_cli/test_telegram_managed_bot.py b/tests/hermes_cli/test_telegram_managed_bot.py index 5b3e805528..7768515e9c 100644 --- a/tests/hermes_cli/test_telegram_managed_bot.py +++ b/tests/hermes_cli/test_telegram_managed_bot.py @@ -36,6 +36,17 @@ class TestQRCode: captured = capsys.readouterr() assert "https://t.me/newbot/Bot/test_bot" in captured.out + def test_print_qr_code_tip_targets_active_interpreter(self, capsys): + # Regression for #111695: a bare `pip install` targets the wrong + # environment when Hermes runs in an isolated venv. The fallback tip + # must name the interpreter that is actually running. + import sys + + with patch.dict("sys.modules", {"qrcode": None}): + print_qr_code("https://t.me/newbot/Bot/test_bot") + captured = capsys.readouterr() + assert sys.executable in captured.out + class TestCreatePairing: def test_success(self):