From 6ceddea179a4b640b2b98927a17c3d3f9a4a6971 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Apr 2026 18:50:12 +0000 Subject: [PATCH] fix(telegram): verify stale PID is a server.ts process before SIGTERM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PID files race with OS PID recycling. The lockfile from #1349 stored only a PID; after enough churn that PID can be reassigned to anything — including the new launch's own bun-run wrapper. SIGTERMing the wrapper closes our stdin and triggers immediate self-shutdown ('replacing stale poller' then 'shutting down' within seconds — matches #1459 item 3). Now check 'ps -p -o args=' contains 'server.ts' before killing. execFileSync (no shell); whole block already try/catch so Windows/ps-missing falls through to just overwriting the lockfile. --- external_plugins/telegram/server.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/external_plugins/telegram/server.ts b/external_plugins/telegram/server.ts index 732218e..f3525e6 100644 --- a/external_plugins/telegram/server.ts +++ b/external_plugins/telegram/server.ts @@ -21,6 +21,7 @@ import type { ReactionTypeEmoji } from 'grammy/types' import { randomBytes } from 'crypto' import { readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync, renameSync, realpathSync, chmodSync } from 'fs' import { homedir } from 'os' +import { execFileSync } from 'child_process' import { join, extname, sep } from 'path' const STATE_DIR = process.env.TELEGRAM_STATE_DIR @@ -63,8 +64,15 @@ try { const stale = parseInt(readFileSync(PID_FILE, 'utf8'), 10) if (stale > 1 && stale !== process.pid) { process.kill(stale, 0) - process.stderr.write(`telegram channel: replacing stale poller pid=${stale}\n`) - process.kill(stale, 'SIGTERM') + // PID files race with OS PID recycling — verify the holder is actually a + // server.ts process before SIGTERM. Otherwise a recycled PID can point at + // our own bun-run wrapper (kills our stdin → immediate self-shutdown) or + // an unrelated user process. + const cmd = execFileSync('ps', ['-p', String(stale), '-o', 'args='], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }) + if (cmd.includes('server.ts')) { + process.stderr.write(`telegram channel: replacing stale poller pid=${stale}\n`) + process.kill(stale, 'SIGTERM') + } } } catch {} writeFileSync(PID_FILE, String(process.pid))