Silent failures Ā· Happened

The long-polling bot that short-polled for 36 days

My Telegram ops bot was meant to hold each request open for 50 seconds. A name collision in my own wrapper made it ask every 3.5 seconds instead, and my DNS sinkhole was the thing that noticed.

Fixed Accurate as of
Stacked bar chart of Telegram bridge poll errors per week from late July to early October 2026. From 700 to 1,925 errors a week through August, almost all 'Network is unreachable'. After the polling fix on September 5 the weekly totals drop to between 4 and 11. Markers show the router root cause found on October 1 and bridge mode on October 5.

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)

  1. 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.
  2. In any API wrapper, name transport settings so they can’t collide with request parameters: sock_timeout, connect_timeout, never bare timeout.
  3. 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.
  4. Reuse connections. One keep-alive connection per daemon is cheaper for you and for the API.
  5. 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.

The week's seven, by email

Entries post here daily. Every Monday, one email with the past week's seven. Unsubscribe anytime.

Let's Connect