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).
This commit is contained in:
spiceoogway 2026-01-30 04:15:03 -05:00
parent 7150268f84
commit 0639c7bf1f
2 changed files with 29 additions and 1 deletions

View File

@ -148,6 +148,19 @@ export async function startGatewayServer(
port = 18789,
opts: GatewayServerOptions = {},
): Promise<GatewayServer> {
// Install global unhandled rejection handler to prevent gateway crashes
// from background promises (e.g., Telegram polling, network operations)
const handleUnhandledRejection = (reason: unknown, promise: Promise<unknown>) => {
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();
}

View File

@ -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)
}
}
}
}