From 2aa90a83876b548c5db2e3ecae22d93088e6e0e5 Mon Sep 17 00:00:00 2001 From: Kenneth Lien Date: Fri, 20 Mar 2026 10:54:33 -0700 Subject: [PATCH] telegram: exit when Claude Code closes the connection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the MCP stdio transport closes, the bot kept polling Telegram as a zombie process — holding the token and causing 409 Conflict for the next session. - Listen for stdin end/close and SIGTERM/SIGINT -> bot.stop() + exit - Force-exit after 2s if bot.stop() stalls on the long-poll timeout - unref the approval-check interval so it doesn't keep us alive Fixes #793, partial #788 (issue 3) --- external_plugins/telegram/server.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/external_plugins/telegram/server.ts b/external_plugins/telegram/server.ts index 8acd52a..c5b0091 100644 --- a/external_plugins/telegram/server.ts +++ b/external_plugins/telegram/server.ts @@ -304,7 +304,7 @@ function checkApprovals(): void { } } -if (!STATIC) setInterval(checkApprovals, 5000) +if (!STATIC) setInterval(checkApprovals, 5000).unref() // Telegram caps messages at 4096 chars. Split long replies, preferring // paragraph boundaries when chunkMode is 'newline'. @@ -507,6 +507,24 @@ mcp.setRequestHandler(CallToolRequestSchema, async req => { await mcp.connect(new StdioServerTransport()) +// When Claude Code closes the MCP connection, stdin gets EOF. Without this +// the bot keeps polling forever as a zombie, holding the token and blocking +// the next session with 409 Conflict. +let shuttingDown = false +function shutdown(): void { + if (shuttingDown) return + shuttingDown = true + process.stderr.write('telegram channel: shutting down\n') + // bot.stop() signals the poll loop to end; the current getUpdates request + // may take up to its long-poll timeout to return. Force-exit after 2s. + setTimeout(() => process.exit(0), 2000) + void Promise.resolve(bot.stop()).finally(() => process.exit(0)) +} +process.stdin.on('end', shutdown) +process.stdin.on('close', shutdown) +process.on('SIGTERM', shutdown) +process.on('SIGINT', shutdown) + bot.on('message:text', async ctx => { await handleInbound(ctx, ctx.message.text, undefined) })