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
This commit is contained in:
Kevin Rajan
2026-09-15 03:06:36 -05:00
committed by Teknium
parent 3250020b34
commit 505b36b7af
5 changed files with 18 additions and 4 deletions
+2 -1
View File
@@ -4,6 +4,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import sys
import time import time
from enum import IntEnum from enum import IntEnum
from typing import Optional, Tuple 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}") print(f" Scan the QR code above, or open this URL directly:\n {url}")
else: else:
print(f" Open this URL in QQ on your phone:\n {url}") 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() print()
while time.monotonic() < deadline: while time.monotonic() < deadline:
try: try:
+1 -1
View File
@@ -76,7 +76,7 @@ def render_qr_terminal(url: str) -> str:
def print_qr_code(url: str, *, include_link: bool = True) -> None: 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 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: if include_link:
print(f" Link: {url}") print(f" Link: {url}")
+2 -1
View File
@@ -30,6 +30,7 @@ import logging
import mimetypes import mimetypes
import os import os
import re import re
import sys
import threading import threading
import time import time
import uuid 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}") print(f"\n Scan the QR code above, or open this URL directly:\n {qr_url}")
else: else:
print(f" Open this URL in Feishu / Lark on your phone:\n\n {qr_url}\n") 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() print()
result = _poll_registration( result = _poll_registration(
device_code=begin["device_code"], interval=begin["interval"], device_code=begin["device_code"], interval=begin["interval"],
+2 -1
View File
@@ -10,6 +10,7 @@ import asyncio
import json import json
import logging import logging
import re import re
import sys
import time import time
import uuid import uuid
from datetime import datetime, timezone 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}") print(f"\n Scan the QR code above, or open this URL directly:\n {page_url}")
except Exception: except Exception:
print(f" Open this URL in WeCom on your phone:\n\n {page_url}\n") 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) print("\n Fetching configuration results...", end="", flush=True)
deadline = time.monotonic() + timeout_seconds deadline = time.monotonic() + timeout_seconds
query_url = f"{_QR_QUERY_URL}?scode={urllib.parse.quote(scode)}" query_url = f"{_QR_QUERY_URL}?scode={urllib.parse.quote(scode)}"
@@ -36,6 +36,17 @@ class TestQRCode:
captured = capsys.readouterr() captured = capsys.readouterr()
assert "https://t.me/newbot/Bot/test_bot" in captured.out 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: class TestCreatePairing:
def test_success(self): def test_success(self):