From 1c88dd7a35665ad30bbbbee33975804d5a61a86b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 31 Mar 2026 16:58:14 +0000 Subject: [PATCH] Add comprehensive error handling to prevent process crashes - Global uncaughtException/unhandledRejection handlers in index.ts - FFmpeg stdout/stderr stream error handlers in player.ts - HTTP server and WebSocket server error handlers in server.ts - Safe WebSocket broadcast with try-catch in websocket.ts - Catch async errors from textMessage handler in instance.ts - Reset voiceFramesSent counter on reconnect in client.ts https://claude.ai/code/session_01EjpEsC2GCsvwbu4n3XC8EE --- src/audio/player.ts | 8 ++++++++ src/bot/instance.ts | 4 +++- src/index.ts | 8 ++++++++ src/ts-protocol/client.ts | 1 + src/web/server.ts | 7 +++++++ src/web/websocket.ts | 6 +++++- 6 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/audio/player.ts b/src/audio/player.ts index e5c52c0..03be44f 100644 --- a/src/audio/player.ts +++ b/src/audio/player.ts @@ -128,6 +128,14 @@ export class AudioPlayer extends EventEmitter { this.logger.info({ ffmpeg: ffmpegBin }, "Using ffmpeg binary"); this.ffmpeg = spawn(ffmpegBin, args, { stdio: ["ignore", "pipe", "pipe"] }); + // Prevent stream errors from crashing the process + this.ffmpeg.stdout!.on("error", (err) => { + this.logger.warn({ err }, "FFmpeg stdout error"); + }); + this.ffmpeg.stderr!.on("error", (err) => { + this.logger.warn({ err }, "FFmpeg stderr error"); + }); + let gotFirstData = false; this.ffmpeg.stdout!.on("data", (chunk: Buffer) => { if (!gotFirstData) { diff --git a/src/bot/instance.ts b/src/bot/instance.ts index 69921ad..0058824 100644 --- a/src/bot/instance.ts +++ b/src/bot/instance.ts @@ -99,7 +99,9 @@ export class BotInstance extends EventEmitter { private setupTsEvents(): void { this.tsClient.on("textMessage", (msg: TS3TextMessage) => { - this.handleTextMessage(msg); + this.handleTextMessage(msg).catch((err) => { + this.logger.error({ err }, "Unhandled error in text message handler"); + }); }); this.tsClient.on("disconnected", () => { diff --git a/src/index.ts b/src/index.ts index 554825a..14beb61 100644 --- a/src/index.ts +++ b/src/index.ts @@ -25,6 +25,14 @@ async function main() { saveConfig(CONFIG_PATH, config); const logger = createLogger(LOG_DIR); + + // Prevent unhandled errors from crashing the process + process.on("uncaughtException", (err) => { + logger.error({ err }, "Uncaught exception"); + }); + process.on("unhandledRejection", (reason) => { + logger.error({ reason }, "Unhandled promise rejection"); + }); const db = createDatabase(DB_PATH); const apiServer = createApiServerManager( diff --git a/src/ts-protocol/client.ts b/src/ts-protocol/client.ts index 1426801..63e5c04 100644 --- a/src/ts-protocol/client.ts +++ b/src/ts-protocol/client.ts @@ -114,6 +114,7 @@ export class TS3Client extends EventEmitter { await this.client.connect(); await this.client.waitConnected(); this.clientId = this.client.clientID(); + this.voiceFramesSent = 0; this.logger.info({ clientId: this.clientId }, "Logged in (visible client)"); // Join default channel if specified diff --git a/src/web/server.ts b/src/web/server.ts index af9780d..7a5621d 100644 --- a/src/web/server.ts +++ b/src/web/server.ts @@ -67,7 +67,14 @@ export function createWebServer(options: WebServerOptions): WebServer { }); } + server.on("error", (err) => { + logger.error({ err }, "HTTP server error"); + }); + const wss = new WebSocketServer({ server, path: "/ws" }); + wss.on("error", (err) => { + logger.error({ err }, "WebSocket server error"); + }); const cleanupWs = setupWebSocket(wss, options.botManager, logger); return { diff --git a/src/web/websocket.ts b/src/web/websocket.ts index 916f820..fc8813d 100644 --- a/src/web/websocket.ts +++ b/src/web/websocket.ts @@ -39,7 +39,11 @@ export function setupWebSocket( const message = JSON.stringify(data); for (const client of clients) { if (client.readyState === WebSocket.OPEN) { - client.send(message); + try { + client.send(message); + } catch { + clients.delete(client); + } } } };