diff --git a/services/discord-gateway/src/app/bootstrap.ts b/services/discord-gateway/src/app/bootstrap.ts index f366c32..c01d169 100644 --- a/services/discord-gateway/src/app/bootstrap.ts +++ b/services/discord-gateway/src/app/bootstrap.ts @@ -222,7 +222,10 @@ export async function initializeDiscordGateway() { await initializeDatabase(); logger.info("PostgreSQL database initialized"); } catch (err) { - logger.error({ error: err }, "Failed to initialize database"); + logger.error( + { err, errorMsg: err instanceof Error ? err.message : String(err) }, + "Failed to initialize database", + ); throw new DatabaseError( `Database initialization failed: ${err instanceof Error ? err.message : String(err)}`, ); @@ -267,7 +270,10 @@ export async function initializeDiscordGateway() { }); client.on("error", (err) => { - logger.error({ error: err }, "Client error"); + logger.error( + { err, errorMsg: err instanceof Error ? err.message : String(err) }, + "Client error", + ); }); process.on("SIGINT", () => { @@ -298,7 +304,14 @@ export async function initializeDiscordGateway() { ); return; } - logger.error({ error: err }, "Uncaught exception"); + logger.error( + { + err, + errorMsg: err instanceof Error ? err.message : String(err), + stack: err?.stack, + }, + "Uncaught exception", + ); gracefulShutdown("uncaughtException"); }); diff --git a/services/discord-gateway/src/goLive/prepareStream.ts b/services/discord-gateway/src/goLive/prepareStream.ts index a5a4af6..2c490c2 100644 --- a/services/discord-gateway/src/goLive/prepareStream.ts +++ b/services/discord-gateway/src/goLive/prepareStream.ts @@ -234,6 +234,10 @@ export function prepareStream( } } + // Safety: proc may error before playStream attaches a demux listener on + // `output`. A no-op listener here prevents an unhandled 'error' event + // on the PassThrough from crashing the gateway on ffmpeg spawn failure. + output.on("error", () => {}); proc.stdout?.pipe(output); proc.stderr?.on("data", () => { /* swallow ffmpeg stderr */ diff --git a/services/discord-gateway/src/modules/voice-recording/screenShareController.ts b/services/discord-gateway/src/modules/voice-recording/screenShareController.ts index f12de4e..fa66b43 100644 --- a/services/discord-gateway/src/modules/voice-recording/screenShareController.ts +++ b/services/discord-gateway/src/modules/voice-recording/screenShareController.ts @@ -93,11 +93,11 @@ export class ScreenShareController { const timer = setTimeout(() => { cleanup(); destroyInput(); - tee.destroy( - new Error( - "Screen input produced no data within 12s — merge likely failed", - ), - ); + // Listeners were just removed by cleanup() — destroying tee WITH + // an error would emit "error" on an unlistened PassThrough and + // surface as an unhandled 'error' event (crash). Destroy + // silently; the error lives in the rejection only. + tee.destroy(); reject( new Error( "Screen input produced no data within 12s — merge likely failed", @@ -131,6 +131,11 @@ export class ScreenShareController { tee.once("end", onEnd); }); + // Safety net: cleanup() removes the once() listeners on timeout/error, + // but a late error event from input.pipe(tee) can still fire on an + // unlistened PassThrough and crash the gateway (unhandled 'error'). + // A permanent no-op listener guarantees the event is always swallowed. + tee.on("error", () => {}); // Pass the tee onward — the encoder consumes the same buffered // stream, so no data from the merge is lost. return tee;