Description
In src/web/routes/auth.py, during Telegram account registration the client is added to the in-memory pool (pool.add_client()) before being saved to the database (db.add_account()). If any exception occurs between those two calls, the account exists in the pool but not in the database. After a process restart the account is gone entirely — the session string is lost.
Location
File: src/web/routes/auth.py
Lines: 142–168
Code
try:
session_string = await auth.verify_code(phone, code, phone_code_hash, password_2fa or None)
existing = await db.get_accounts()
is_primary = len(existing) == 0
await pool.add_client(phone, session_string) # ← (1) added to in-memory pool
is_premium = False
acquired = await pool.get_client_by_phone(phone) # ← (2) can raise / timeout
if acquired:
session, acquired_phone = acquired
try:
me = await session.fetch_me() # ← (3) network call — can fail
is_premium = bool(getattr(me, "premium", False))
except Exception as e:
logger.warning(...)
finally:
await pool.release_client(acquired_phone)
account = Account(...)
await db.add_account(account) # ← (4) only persisted here
Failure scenarios
| When it fails |
Result |
fetch_me() raises an unhandled non-Exception (e.g. BaseException, KeyboardInterrupt) |
Pool has client, DB doesn't |
db.add_account() raises (DB locked, disk full, unique constraint) |
Pool has client, DB doesn't |
| Process killed (SIGKILL) between steps 1 and 4 |
Pool state lost on restart; session string never persisted |
On restart: ClientPool is rebuilt from the database, so the orphaned in-memory client disappears — the authenticated session is permanently lost even though the user completed the 2FA flow successfully.
Additional problem: is_primary race
is_primary = len(existing) == 0 is computed before pool.add_client(). If two accounts are added concurrently, both may read existing as empty and both will be marked as primary.
Fix
- Save to the database first, then add to the pool:
account = Account(phone=phone, session_string=session_string,
is_primary=is_primary, is_premium=False)
await db.add_account(account) # persist first
await pool.add_client(phone, session_string) # then warm pool
# update is_premium in background / best-effort
- If pool insertion fails after a successful DB write, that is recoverable on restart — the inverse (pool ✅, DB ❌) is not.
Severity
CRITICAL — authenticated session strings can be permanently lost, causing invisible account drop without any error shown to the user.
Description
In
src/web/routes/auth.py, during Telegram account registration the client is added to the in-memory pool (pool.add_client()) before being saved to the database (db.add_account()). If any exception occurs between those two calls, the account exists in the pool but not in the database. After a process restart the account is gone entirely — the session string is lost.Location
File:
src/web/routes/auth.pyLines: 142–168
Code
Failure scenarios
fetch_me()raises an unhandled non-Exception(e.g.BaseException,KeyboardInterrupt)db.add_account()raises (DB locked, disk full, unique constraint)On restart:
ClientPoolis rebuilt from the database, so the orphaned in-memory client disappears — the authenticated session is permanently lost even though the user completed the 2FA flow successfully.Additional problem:
is_primaryraceis_primary = len(existing) == 0is computed beforepool.add_client(). If two accounts are added concurrently, both may readexistingas empty and both will be marked as primary.Fix
Severity
CRITICAL — authenticated session strings can be permanently lost, causing invisible account drop without any error shown to the user.