From 0639c7bf1f37bafeb847afc9e422f05f3bb084a3 Mon Sep 17 00:00:00 2001 From: spiceoogway Date: Fri, 30 Jan 2026 04:15:03 -0500 Subject: [PATCH 1/2] Fix #4501: Handle unhandled promise rejections in Telegram polling - Add global unhandled rejection handler in gateway startup - Properly await runner.stop() in Telegram monitor to prevent unhandled rejections - Ensure runner cleanup in finally block to prevent lingering promises - Log rejections instead of crashing the gateway Fixes issue where Telegram Bot API network failures during idle polling would cause unhandled promise rejections and crash the gateway process. The fix adds defensive error handling at both the gateway level (global handler) and the Telegram provider level (proper promise cleanup). --- src/gateway/server.impl.ts | 16 ++++++++++++++++ src/telegram/monitor.ts | 14 +++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/gateway/server.impl.ts b/src/gateway/server.impl.ts index efa91be76..a793966b6 100644 --- a/src/gateway/server.impl.ts +++ b/src/gateway/server.impl.ts @@ -148,6 +148,19 @@ export async function startGatewayServer( port = 18789, opts: GatewayServerOptions = {}, ): Promise { + // Install global unhandled rejection handler to prevent gateway crashes + // from background promises (e.g., Telegram polling, network operations) + const handleUnhandledRejection = (reason: unknown, promise: Promise) => { + const formatted = reason instanceof Error ? reason.message : String(reason); + log.error(`unhandled promise rejection: ${formatted}`); + // Log additional details for debugging + if (reason instanceof Error && reason.stack) { + log.debug(`rejection stack: ${reason.stack}`); + } + // Don't crash the gateway - log and continue + }; + process.on("unhandledRejection", handleUnhandledRejection); + // Ensure all default port derivations (browser/canvas) see the actual runtime port. process.env.OPENCLAW_GATEWAY_PORT = String(port); logAcceptedEnvOption({ @@ -572,6 +585,9 @@ export async function startGatewayServer( return { close: async (opts) => { + // Remove unhandled rejection handler on shutdown + process.off("unhandledRejection", handleUnhandledRejection); + if (diagnosticsEnabled) { stopDiagnosticHeartbeat(); } diff --git a/src/telegram/monitor.ts b/src/telegram/monitor.ts index 2709b591b..17ea74a18 100644 --- a/src/telegram/monitor.ts +++ b/src/telegram/monitor.ts @@ -161,7 +161,12 @@ export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) { const runner = run(bot, createTelegramRunnerOptions(cfg)); const stopOnAbort = () => { if (opts.abortSignal?.aborted) { - void runner.stop(); + // Properly await runner.stop() to prevent unhandled rejections + runner.stop().catch((err) => { + (opts.runtime?.error ?? console.error)( + `telegram: runner stop failed: ${formatErrorMessage(err)}`, + ); + }); } }; opts.abortSignal?.addEventListener("abort", stopOnAbort, { once: true }); @@ -194,6 +199,13 @@ export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) { } } finally { opts.abortSignal?.removeEventListener("abort", stopOnAbort); + // Ensure runner is stopped to prevent lingering promises + try { + await runner.stop(); + } catch (stopErr) { + // Suppress errors from runner.stop() in finally block + // (already logged by stopOnAbort if abort-triggered) + } } } } From a4cccc2231392b6a3ae71ded15248e7ca8f7f8cd Mon Sep 17 00:00:00 2001 From: spiceoogway Date: Fri, 30 Jan 2026 08:08:52 -0500 Subject: [PATCH 2/2] fix: resolve lint errors - unused parameters and type safety --- openclaw | 1 + src/commands/onboard-auth.credentials.ts | 3 ++- src/gateway/server.impl.ts | 4 ++-- src/telegram/monitor.ts | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) create mode 160000 openclaw diff --git a/openclaw b/openclaw new file mode 160000 index 000000000..0639c7bf1 --- /dev/null +++ b/openclaw @@ -0,0 +1 @@ +Subproject commit 0639c7bf1f37bafeb847afc9e422f05f3bb084a3 diff --git a/src/commands/onboard-auth.credentials.ts b/src/commands/onboard-auth.credentials.ts index fbf6dbfb9..08dba7fee 100644 --- a/src/commands/onboard-auth.credentials.ts +++ b/src/commands/onboard-auth.credentials.ts @@ -10,8 +10,9 @@ export async function writeOAuthCredentials( agentDir?: string, ): Promise { // Write to resolved agent dir so gateway finds credentials on startup. + const emailStr = typeof creds.email === "string" ? creds.email : "default"; upsertAuthProfile({ - profileId: `${provider}:${creds.email ?? "default"}`, + profileId: `${provider}:${emailStr}`, credential: { type: "oauth", provider, diff --git a/src/gateway/server.impl.ts b/src/gateway/server.impl.ts index a793966b6..5e8d2ae97 100644 --- a/src/gateway/server.impl.ts +++ b/src/gateway/server.impl.ts @@ -150,7 +150,7 @@ export async function startGatewayServer( ): Promise { // Install global unhandled rejection handler to prevent gateway crashes // from background promises (e.g., Telegram polling, network operations) - const handleUnhandledRejection = (reason: unknown, promise: Promise) => { + const handleUnhandledRejection = (reason: unknown, _promise: Promise) => { const formatted = reason instanceof Error ? reason.message : String(reason); log.error(`unhandled promise rejection: ${formatted}`); // Log additional details for debugging @@ -587,7 +587,7 @@ export async function startGatewayServer( close: async (opts) => { // Remove unhandled rejection handler on shutdown process.off("unhandledRejection", handleUnhandledRejection); - + if (diagnosticsEnabled) { stopDiagnosticHeartbeat(); } diff --git a/src/telegram/monitor.ts b/src/telegram/monitor.ts index 17ea74a18..abb52ce31 100644 --- a/src/telegram/monitor.ts +++ b/src/telegram/monitor.ts @@ -202,7 +202,7 @@ export async function monitorTelegramProvider(opts: MonitorTelegramOpts = {}) { // Ensure runner is stopped to prevent lingering promises try { await runner.stop(); - } catch (stopErr) { + } catch { // Suppress errors from runner.stop() in finally block // (already logged by stopOnAbort if abort-triggered) }