Initial commit — tg-stream, tg-task, Claude Code hooks
This commit is contained in:
57
docs/telegram-plugin-duplicate-poller-bug.md
Normal file
57
docs/telegram-plugin-duplicate-poller-bug.md
Normal file
@@ -0,0 +1,57 @@
|
||||
# Bug: Telegram plugin spawns one poller per Claude session → silent message drops
|
||||
|
||||
**Plugin:** `claude-plugins-official/external_plugins/telegram`
|
||||
**Symptom:** When two or more Claude Code sessions are running concurrently, incoming Telegram messages to the bot are randomly dropped — the user sends N messages, the bot only sees a subset, and replies arrive late or not at all. No error is surfaced anywhere.
|
||||
|
||||
## Root cause
|
||||
|
||||
Each Claude Code session starts its own copy of the Telegram plugin via:
|
||||
|
||||
```
|
||||
bun run --cwd <plugin-path> --shell=bun --silent start
|
||||
```
|
||||
|
||||
Observed on this host (two concurrent sessions):
|
||||
|
||||
```
|
||||
PID 25 pts/0 claude
|
||||
PID 58 pts/0 bun run .../external_plugins/telegram start
|
||||
PID 487 pts/1 claude
|
||||
PID 588 pts/1 bun run .../external_plugins/telegram start
|
||||
```
|
||||
|
||||
Both bun processes long-poll Telegram's `getUpdates` against the **same bot token**. Per Telegram Bot API semantics and the known tdlib issue [tdlib/telegram-bot-api#43](https://github.com/tdlib/telegram-bot-api/issues/43), concurrent `getUpdates` calls against one token race: whichever call acks an update first marks it confirmed server-side, and the other poller never sees it. With N concurrent pollers, on average ~(N-1)/N of any individual poller's "view" is missing updates. Since the plugin in any given Claude session only acts on what *its* poller sees, messages get silently dropped from the user's perspective.
|
||||
|
||||
This is not a Telegram bug — Telegram's `getUpdates` is documented as single-consumer. It's a plugin architecture bug.
|
||||
|
||||
## Reproduction
|
||||
|
||||
1. Open two Claude Code sessions on the same machine that both load this plugin (same bot token).
|
||||
2. From a Telegram user paired to the bot, send 5 messages back-to-back.
|
||||
3. Observe: only some messages produce a reply; the rest vanish without trace.
|
||||
4. Kill one of the two `bun .../telegram start` processes.
|
||||
5. Resend 5 messages — all arrive and get replies.
|
||||
|
||||
Confirmed reproducible on this host (2026-04-06): killing PID 58 immediately fixed message delivery for the surviving session.
|
||||
|
||||
## Why no logs
|
||||
|
||||
`/root/.claude/plugins/data/telegram-claude-plugins-official/` is empty — the plugin emits no per-message audit log, so the drop is invisible unless you happen to compare what the user sent vs what the agent saw.
|
||||
|
||||
## Suggested fix
|
||||
|
||||
Plugin needs a singleton lock per bot token. Options:
|
||||
|
||||
1. **File lock** (`flock` on `/tmp/telegram-plugin-<token-hash>.lock`) — first plugin instance to start grabs the lock and runs the poller; subsequent instances detect the lock and instead attach to a local Unix socket / named pipe owned by the leader to receive updates fan-out. On leader exit, a follower takes over.
|
||||
2. **Webhook mode** instead of long polling — Telegram delivers each update exactly once to the configured URL, sidestepping the race entirely. Requires a public endpoint or tunnel, so harder to set up but more robust.
|
||||
3. **Out-of-process daemon** — ship the poller as a separate long-lived service (systemd unit / docker container) and have each Claude session's plugin instance act as a thin client that subscribes to it. Cleanest separation of concerns.
|
||||
|
||||
Option 1 is the smallest change. Option 3 is the right long-term shape, especially since multiple Claude sessions are clearly an intended use case.
|
||||
|
||||
## Workaround until fix lands
|
||||
|
||||
Run a single Claude session, OR manually `kill` all but one `bun run .../telegram start` process whenever a second session starts. Add a startup hook that does this automatically.
|
||||
|
||||
## Also worth logging
|
||||
|
||||
The plugin should write a per-message log line (`update_id received`, `chat_id`, `acted/ignored`) to `plugins/data/telegram-claude-plugins-official/`. Even one line per inbound update would have saved hours of guessing here.
|
||||
Reference in New Issue
Block a user