The short version: The Telegram bot that runs my homelabās ops chat is supposed to long-poll: ask the API for updates and let the request hang for up to 50 seconds. For 36 days it asked every 3.5 seconds. The 50 was landing on my HTTP wrapperās socket timeout instead of reaching Telegram, because both arguments were called timeout. Pi-holeās query log showed about 88,000 lookups a day for one hostname, and that is how I found it.
Get the week's seven in one email, every Monday.
What went wrong
The bridge is a small Python daemon. It has an api() helper that wraps every call to Telegram, and that helper took a timeout argument for the socket. Telegramās getUpdates method also takes a timeout parameter, the one that makes long polling long. The poll loop called the helper with timeout=50. Python matched the 50 to the helperās own parameter, Telegram never saw it, and every request returned at once.
Nothing failed. The bot answered commands, the heartbeat line kept arriving, and the only trace was volume: 63 to 67 DNS lookups a minute for api.telegram.org, from a process that should have needed about one.
What we learned
- A sinkholeās query log is a free profiler. Pi-hole counts every lookup by client and domain. A daemon that resolves the same name 88,000 times a day stands out there and nowhere else.
- Donāt give a transport argument the same name as an API parameter. The collision was invisible at the call site, which read exactly like the documentation.
- Fixing the symptom hid another bug. The bot logged 8,721 poll errors in those five weeks, and 8,025 of them were āNetwork is unreachableā. Opening a new connection every few seconds gave a flaky route thousands of chances to fail. After the fix the errors fell to a handful a week, and I treated that as solved. The real cause was my router, and it took until October 1 to find it. That story is its own entry.
What went right
Once seen, the fix took an hour on September 5. The socket argument became _sock_timeout, so timeout=50 reaches Telegram, and the bridge keeps one HTTPS connection open instead of making a new one per poll. Lookups dropped to 1 to 4 a minute, the heartbeat settled at about every 50 seconds, and the poll-error line in the audit log went quiet. Weekly poll errors went from 700 to 1,925 through August to between 4 and 11 afterwards; 93 in the eight weeks since.
How to do it (as of October 2026)
- Open your Pi-hole or routerās query log and sort by client and domain. Anything your own daemons resolve more than a few times a minute deserves an explanation.
- In any API wrapper, name transport settings so they canāt collide with request parameters:
sock_timeout,connect_timeout, never baretimeout. - For long polling, log the measured gap between polls at startup. If a 50-second poll keeps returning in under a second, say so loudly.
- Reuse connections. One keep-alive connection per daemon is cheaper for you and for the API.
- When a flood of errors disappears after an unrelated fix, write down that it disappeared, not that it was solved.
Status
Fixed on September 5. The router behind the āunreachableā errors is covered in the router entry.