Skip to content

Graduate TDLib large-media downloads to production (main account) - #17

Merged
speech115 merged 19 commits into
mainfrom
worktree-tdlib-media-download-poc
Jul 1, 2026
Merged

Graduate TDLib large-media downloads to production (main account)#17
speech115 merged 19 commits into
mainfrom
worktree-tdlib-media-download-poc

Conversation

@speech115

Copy link
Copy Markdown
Owner

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 the main account 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.md
Plan: mcp/docs/superpowers/plans/2026-07-01-tdlib-large-media-download-implementation.md

  • locking.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-imported pytdbot client wiring. pytdbot/tdjson are 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_backend event) into the existing download flow.
  • mcp/scripts/tdlib_login.py (new): one-time interactive login for the main account's TDLib session, promoted from the POC script.
  • docs/operator-workflows.md: rollout steps for enabling the capability.

TELEGRAM_TDLIB_ENABLED defaults to unset/false — fully reversible, off until an operator opts in.

Test plan

  • Full suite green throughout: 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 since pytdbot isn't installed in this dev/CI env, by design)
  • Each of the 5 tasks implemented via TDD, individually reviewed (spec compliance + code quality) and approved
  • Final whole-branch review (cross-task integration, signature matching, lazy-import discipline, isolation-guard fix verification) — approved, ready to merge
  • Before enabling in production: re-run the live TDLib-vs-Telethon comparison once against a fresh set of large files to confirm the measured advantage holds outside the original 3-file POC sample (per design doc's Testing section)

Known minor follow-ups (non-blocking, noted in final review): threshold_mb env var parsing could raise on operator typo before the fallback-guarded block; shutil.copy2 failure is telemetry-labeled as a TDLib fallback reason; no automated integration test for the full routing→fallback control flow (matches this repo's pre-existing download_post.py testing boundary).

🤖 Generated with Claude Code

speech115 and others added 19 commits July 1, 2026 13:07
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.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +101 to +102
lock = FileSessionLock(session_dir / "download.lock")
if not try_acquire_with_timeout(lock, timeout_seconds=5.0):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@speech115
speech115 merged commit b0305df into main Jul 1, 2026
2 checks passed
@speech115
speech115 deleted the worktree-tdlib-media-download-poc branch July 1, 2026 15:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant