Graduate TDLib large-media downloads to production (main account) - #17
Conversation
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pytdbot.Client.start() only auto-answers authorizationStateWaitPhoneNumber for bot-token logins; for a real user account it no-ops there, so the original login_tdlib.py would hang forever. Rewrite it to drive the phone -> code -> (password) state machine explicitly across separate invocations, since a live code can't be fed into a blocking input() from outside a real TTY.
…ted stdout
Live run revealed tg download --json interleaves PROGRESS lines with the
JSON envelope on stdout even with --json set, so json.loads(proc.stdout)
always failed and every download was misreported as ok=False despite
succeeding. Extract from the first '{' instead. Also confirmed live: the
real payload uses "path"/"size_bytes" keys, not "local_path" - read
size_bytes directly rather than re-stat()ing when present.
Live run crashed with AttributeError: 'MessageVideo' object has no attribute 'get'. pytdbot's to_dict() only converts the top level of an object; nested fields (Message.content, File.local) stay native typed objects. Drop to_dict()/.get() everywhere in the TDLib path and use native attribute/subscript access instead (pytdbot types support both). Rewrote tests to construct real pytdbot.types objects instead of dict fixtures, since the dict fixtures were exactly what hid this bug.
Task-by-task plan executing the production graduation design (mcp/docs/superpowers/specs/2026-07-01-tdlib-large-media-download-design.md) after the POC passed its gate (+78.7% faster, resume confirmed).
…tdbot backend Adds should_route_to_tdlib/download_via_tdlib pure-logic + optional pytdbot client wiring for the main-account-only TDLib large-media-download backend. pytdbot stays an optional [tdlib] extra; every pytdbot-dependent function imports it lazily so the module (and its non-pytdbot tests) work without it installed. Fixed the isolation-directory marker check to compare path segments rather than a raw substring, since a naive substring check on ".telegram-mcp" would also match the isolated ".telegram-mcp-tdlib" tree.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ee0a023cde
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| api_hash=os.environ["TELEGRAM_API_HASH"], | ||
| files_directory=str(session_dir), | ||
| ) | ||
| await client.start() |
There was a problem hiding this comment.
Prevent TDLib auth waits from blocking fallback
When TELEGRAM_TDLIB_ENABLED=true but the TDLib user session is missing or expired, this default client.start() waits for authorization to reach ready; unlike the login helper, this path supplies no phone/code/password, so the await can block indefinitely and never returns to download_post's exception handler. In that stale-session scenario tg download hangs instead of falling back to Telethon, so start without waiting (or add a bounded readiness check) and raise when the session is not ready.
Useful? React with 👍 / 👎.
| lock = FileSessionLock(session_dir / "download.lock") | ||
| if not try_acquire_with_timeout(lock, timeout_seconds=5.0): |
There was a problem hiding this comment.
Validate TDLib isolation before acquiring locks
If TELEGRAM_TDLIB_SESSION_DIR is accidentally pointed at ~/.telegram-mcp/..., this creates/acquires download.lock in the Telethon session tree before build_client() runs assert_isolated_from_telethon. That means the guard still writes into the directory it is meant to avoid touching, leaving a new file in Telethon-owned state; validate the session directory before constructing or acquiring this lock.
Useful? React with 👍 / 👎.
Summary
Graduates the isolated TDLib media-download POC (gated at +78.7% faster than Telethon, resumability confirmed on 3 real files) into a narrowly-scoped production capability:
tg download/download_post()now auto-routes large media downloads on themainaccount through TDLib, with every failure mode falling back to Telethon using the already-open connection. Everything else — reads, search, sends, MCP tool downloads (download_media_batch,download_dialog_media) — stays on Telethon, unchanged.Design:
mcp/docs/superpowers/specs/2026-07-01-tdlib-large-media-download-design.mdPlan:
mcp/docs/superpowers/plans/2026-07-01-tdlib-large-media-download-implementation.mdlocking.py: timeout-bounded lock acquisition (try_acquire_with_timeout) so a concurrent large download serializes at the TDLib layer instead of racing on the session DB, falling back to Telethon if the lock isn't free within 5s.tdlib_download.py(new): routing decision (account/enabled/content-type/size threshold), isolation guard against the Telethon session tree, and lazy-importedpytdbotclient wiring.pytdbot/tdjsonare an optional[tdlib]extras group — never a hard dependency, never imported at module load time.download_post.py: wires routing + TDLib attempt + Telethon fallback + telemetry (download_post_backendevent) into the existing download flow.mcp/scripts/tdlib_login.py(new): one-time interactive login for themainaccount's TDLib session, promoted from the POC script.docs/operator-workflows.md: rollout steps for enabling the capability.TELEGRAM_TDLIB_ENABLEDdefaults to unset/false — fully reversible, off until an operator opts in.Test plan
cd mcp && TELEGRAM_API_ID=1 TELEGRAM_API_HASH=hash PYTHONPATH=src .venv/bin/python -m unittest discover -s tests -p 'test_*.py'→ 402 passed, 5 skipped (pytdbot-dependent tests skip sincepytdbotisn't installed in this dev/CI env, by design)Known minor follow-ups (non-blocking, noted in final review):
threshold_mbenv var parsing could raise on operator typo before the fallback-guarded block;shutil.copy2failure is telemetry-labeled as a TDLib fallback reason; no automated integration test for the full routing→fallback control flow (matches this repo's pre-existingdownload_post.pytesting boundary).🤖 Generated with Claude Code