2323from mcp .shared .exceptions import McpError
2424from pythonjsonlogger import jsonlogger
2525from telethon import TelegramClient , functions , types , utils
26+ from telethon .errors import AuthKeyDuplicatedError
2627from telethon .sessions import StringSession
2728from telethon .tl .types import (
2829 User ,
@@ -396,13 +397,16 @@ def _acquire_session(pool: List[str]) -> str:
396397 pass
397398 print (f"Using Telegram session slot { idx + 1 } /{ len (pool )} ." , file = sys .stderr )
398399 return session
399- print (
400- f"WARNING: all { len (pool )} pooled Telegram session(s) are already in use "
401- "by other clients; reusing the first (may raise AuthKeyDuplicatedError). "
402- "Add another session to TELEGRAM_SESSION_STRINGS to run more clients." ,
403- file = sys .stderr ,
400+ # Handing out an already-claimed session here would make Telegram burn it
401+ # with AuthKeyDuplicatedError — losing the slot for the client that owns it
402+ # too. Refusing to start is recoverable; a burned session is not.
403+ raise RuntimeError (
404+ f"All { len (pool )} pooled Telegram session(s) are already claimed by other "
405+ "live clients, so this one has no session to use. Add another session to "
406+ "TELEGRAM_SESSION_STRINGS (generate it with "
407+ "`uv run session_string_generator.py`) — one slot per concurrent client — "
408+ "or stop one of the other clients."
404409 )
405- return pool [0 ]
406410
407411
408412def _discover_accounts () -> dict [str , TelegramClient ]:
@@ -524,6 +528,7 @@ async def _call_for(label):
524528
525529_last_conn_verified : dict [int , float ] = {}
526530_CONN_VERIFY_INTERVAL : float = 30.0 # seconds between live pings
531+ _RECONNECT_TIMEOUT : float = 30.0 # seconds before a reconnect attempt is abandoned
527532
528533
529534async def _force_reconnect (cl : TelegramClient ):
@@ -534,10 +539,26 @@ async def _force_reconnect(cl: TelegramClient):
534539 await cl .disconnect ()
535540 except Exception :
536541 pass
537- await cl .connect ()
542+ try :
543+ await asyncio .wait_for (cl .connect (), timeout = _RECONNECT_TIMEOUT )
544+ except AuthKeyDuplicatedError as exc :
545+ # Telegram permanently invalidates an auth key used from two IPs at
546+ # once, so retrying here can never succeed — surface it instead of
547+ # letting the caller sit in a reconnect loop.
548+ raise RuntimeError (
549+ "Telegram session is no longer usable: the same session string was "
550+ "used by another client at the same time (AuthKeyDuplicatedError). "
551+ "Give each concurrent client its own session via "
552+ "TELEGRAM_SESSION_STRINGS or TELEGRAM_SESSION_STRING_<LABEL>, then "
553+ "regenerate the burned session with `uv run session_string_generator.py`."
554+ ) from exc
555+ except asyncio .TimeoutError as exc :
556+ raise RuntimeError (
557+ f"Reconnecting to Telegram timed out after { _RECONNECT_TIMEOUT :.0f} s."
558+ ) from exc
538559 if not await cl .is_user_authorized ():
539560 reconnect_logger .warning ("Client not authorized after reconnect, calling start()..." )
540- await cl .start ()
561+ await asyncio . wait_for ( cl .start (), timeout = _RECONNECT_TIMEOUT )
541562 _last_conn_verified [id (cl )] = time .time ()
542563 reconnect_logger .warning ("Forced reconnect successful" )
543564
0 commit comments