Telegram channel plugin: stale-poller cleanup is POSIX-only, leaks orphan getUpdates pollers on Windows that starve other listeners
Summary
The official Telegram channel plugin (telegram@claude-plugins-official, v0.0.7) leaks an orphaned server.ts polling process per Claude Code session on Windows, because its stale-poller cleanup is POSIX-only. The orphans outlive their sessions and silently steal the bot's single getUpdates slot from any legitimate listener sharing the token (e.g., a persistent claude --channels service on a Linux host), which then loops on 409 Conflict and appears healthy while never receiving a message.
Environment
- Claude Code 2.1.251 (also reproduced with the listener on 2.1.241)
- Telegram plugin
claude-plugins-official/telegram0.0.7 - Windows 11 Home (orphan side) + Ubuntu VPS (starved listener side), one bot token used on both
Root cause
server.ts guards against a stale poller holding the getUpdates slot by reading bot.pid and verifying the old PID before SIGTERM:
const cmd = execFileSync('ps', ['-p', String(stale), '-o', 'args='], { encoding: 'utf8', ... })
if (cmd.includes('server.ts')) {
process.kill(stale, 'SIGTERM')
}
On Windows there is no ps; execFileSync throws, the surrounding try/catch swallows it, and the stale poller is never killed. Additionally, when a session exits uncleanly the poller survives as an orphan. The two together mean every Claude Code session on Windows with the plugin configured can leave a permanent background poller, and they accumulate: we found 7 orphaned bun server.ts processes spanning Aug 29–30 sessions, all long-polling the same token.
Impact observed
A persistent Telegram listener (claude --channels under systemd on a Linux VPS, same bot token) went silent for ~2 days while looking completely healthy: unit active, process tree intact, TCP connections to Telegram established, and pending_update_count always 0 (an orphan consumed each message). The listener's real state was only visible via strace on the bridge process:
telegram channel: 409 Conflict, retrying in 0s
telegram channel: polling as @<bot>
telegram channel: 409 Conflict, retrying in 0s
...
This 409 loop never surfaces in journald or --debug-file output, which made the failure look like a hung Claude session and cost us a long false trail (version rollback, debug instrumentation) before syscall tracing found it.
Repro
- Configure the Telegram channel on a Windows machine (
~/.claude/channels/telegram/.envwith a bot token). - Start and close several Claude Code sessions with the plugin enabled (unclean exit makes it more likely, but accumulation happens regardless since cleanup never works).
- Observe multiple
bun server.tsprocesses persisting after their sessions have ended (Get-CimInstance Win32_Process -Filter "Name='bun.exe'"). - Run a second listener elsewhere with the same token: it loops on 409 Conflict and receives nothing.
Suggested fixes
- Make the stale-PID verification cross-platform (on
win32, verify viawmic/Get-CimInstance/tasklistinstead ofps, or store the process start time alongside the PID and compare). - Consider surfacing repeated 409 Conflicts to the host process as an ERROR-level event rather than only bridge stderr — a listener that can never poll currently looks indistinguishable from a healthy idle one from the host's perspective.
- Ensure the poller exits when its host session ends (tie lifetime to the stdio pipe: exit on stdin EOF).