refactor: centralize unhandled rejection setup
This commit is contained in:
@@ -6,7 +6,7 @@ import { normalizeEnv } from "../infra/env.js";
|
|||||||
import { isMainModule } from "../infra/is-main.js";
|
import { isMainModule } from "../infra/is-main.js";
|
||||||
import { ensureClawdbotCliOnPath } from "../infra/path-env.js";
|
import { ensureClawdbotCliOnPath } from "../infra/path-env.js";
|
||||||
import { assertSupportedRuntime } from "../infra/runtime-guard.js";
|
import { assertSupportedRuntime } from "../infra/runtime-guard.js";
|
||||||
import { isUnhandledRejectionHandled } from "../infra/unhandled-rejections.js";
|
import { installUnhandledRejectionHandler } from "../infra/unhandled-rejections.js";
|
||||||
import { enableConsoleCapture } from "../logging.js";
|
import { enableConsoleCapture } from "../logging.js";
|
||||||
|
|
||||||
export async function runCli(argv: string[] = process.argv) {
|
export async function runCli(argv: string[] = process.argv) {
|
||||||
@@ -25,14 +25,7 @@ export async function runCli(argv: string[] = process.argv) {
|
|||||||
|
|
||||||
// Global error handlers to prevent silent crashes from unhandled rejections/exceptions.
|
// Global error handlers to prevent silent crashes from unhandled rejections/exceptions.
|
||||||
// These log the error and exit gracefully instead of crashing without trace.
|
// These log the error and exit gracefully instead of crashing without trace.
|
||||||
process.on("unhandledRejection", (reason, _promise) => {
|
installUnhandledRejectionHandler();
|
||||||
if (isUnhandledRejectionHandled(reason)) return;
|
|
||||||
console.error(
|
|
||||||
"[clawdbot] Unhandled promise rejection:",
|
|
||||||
reason instanceof Error ? (reason.stack ?? reason.message) : reason,
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on("uncaughtException", (error) => {
|
process.on("uncaughtException", (error) => {
|
||||||
console.error(
|
console.error(
|
||||||
|
|||||||
11
src/index.ts
11
src/index.ts
@@ -27,7 +27,7 @@ import {
|
|||||||
PortInUseError,
|
PortInUseError,
|
||||||
} from "./infra/ports.js";
|
} from "./infra/ports.js";
|
||||||
import { assertSupportedRuntime } from "./infra/runtime-guard.js";
|
import { assertSupportedRuntime } from "./infra/runtime-guard.js";
|
||||||
import { isUnhandledRejectionHandled } from "./infra/unhandled-rejections.js";
|
import { installUnhandledRejectionHandler } from "./infra/unhandled-rejections.js";
|
||||||
import { enableConsoleCapture } from "./logging.js";
|
import { enableConsoleCapture } from "./logging.js";
|
||||||
import { runCommandWithTimeout, runExec } from "./process/exec.js";
|
import { runCommandWithTimeout, runExec } from "./process/exec.js";
|
||||||
import { monitorWebProvider } from "./provider-web.js";
|
import { monitorWebProvider } from "./provider-web.js";
|
||||||
@@ -79,14 +79,7 @@ const isMain = isMainModule({
|
|||||||
if (isMain) {
|
if (isMain) {
|
||||||
// Global error handlers to prevent silent crashes from unhandled rejections/exceptions.
|
// Global error handlers to prevent silent crashes from unhandled rejections/exceptions.
|
||||||
// These log the error and exit gracefully instead of crashing without trace.
|
// These log the error and exit gracefully instead of crashing without trace.
|
||||||
process.on("unhandledRejection", (reason, _promise) => {
|
installUnhandledRejectionHandler();
|
||||||
if (isUnhandledRejectionHandled(reason)) return;
|
|
||||||
console.error(
|
|
||||||
"[clawdbot] Unhandled promise rejection:",
|
|
||||||
reason instanceof Error ? (reason.stack ?? reason.message) : reason,
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on("uncaughtException", (error) => {
|
process.on("uncaughtException", (error) => {
|
||||||
console.error(
|
console.error(
|
||||||
|
|||||||
14
src/infra/bonjour-ciao.ts
Normal file
14
src/infra/bonjour-ciao.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { logDebug } from "../logger.js";
|
||||||
|
|
||||||
|
import { formatBonjourError } from "./bonjour-errors.js";
|
||||||
|
|
||||||
|
export function ignoreCiaoCancellationRejection(reason: unknown): boolean {
|
||||||
|
const message = formatBonjourError(reason).toUpperCase();
|
||||||
|
if (!message.includes("CIAO ANNOUNCEMENT CANCELLED")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logDebug(
|
||||||
|
`bonjour: ignoring unhandled ciao rejection: ${formatBonjourError(reason)}`,
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
7
src/infra/bonjour-errors.ts
Normal file
7
src/infra/bonjour-errors.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export function formatBonjourError(err: unknown): string {
|
||||||
|
if (err instanceof Error) {
|
||||||
|
const msg = err.message || String(err);
|
||||||
|
return err.name && err.name !== "Error" ? `${err.name}: ${msg}` : msg;
|
||||||
|
}
|
||||||
|
return String(err);
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ import os from "node:os";
|
|||||||
|
|
||||||
import { logDebug, logWarn } from "../logger.js";
|
import { logDebug, logWarn } from "../logger.js";
|
||||||
import { getLogger } from "../logging.js";
|
import { getLogger } from "../logging.js";
|
||||||
|
import { ignoreCiaoCancellationRejection } from "./bonjour-ciao.js";
|
||||||
|
import { formatBonjourError } from "./bonjour-errors.js";
|
||||||
import { registerUnhandledRejectionHandler } from "./unhandled-rejections.js";
|
import { registerUnhandledRejectionHandler } from "./unhandled-rejections.js";
|
||||||
|
|
||||||
export type GatewayBonjourAdvertiser = {
|
export type GatewayBonjourAdvertiser = {
|
||||||
@@ -45,14 +47,6 @@ type BonjourService = {
|
|||||||
serviceState: string;
|
serviceState: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
function formatBonjourError(err: unknown): string {
|
|
||||||
if (err instanceof Error) {
|
|
||||||
const msg = err.message || String(err);
|
|
||||||
return err.name && err.name !== "Error" ? `${err.name}: ${msg}` : msg;
|
|
||||||
}
|
|
||||||
return String(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
function serviceSummary(label: string, svc: BonjourService): string {
|
function serviceSummary(label: string, svc: BonjourService): string {
|
||||||
let fqdn = "unknown";
|
let fqdn = "unknown";
|
||||||
let hostname = "unknown";
|
let hostname = "unknown";
|
||||||
@@ -147,16 +141,7 @@ export async function startGatewayBonjourAdvertiser(
|
|||||||
let ciaoCancellationRejectionHandler: (() => void) | undefined;
|
let ciaoCancellationRejectionHandler: (() => void) | undefined;
|
||||||
if (services.length > 0) {
|
if (services.length > 0) {
|
||||||
ciaoCancellationRejectionHandler = registerUnhandledRejectionHandler(
|
ciaoCancellationRejectionHandler = registerUnhandledRejectionHandler(
|
||||||
(reason) => {
|
ignoreCiaoCancellationRejection,
|
||||||
const message = formatBonjourError(reason).toUpperCase();
|
|
||||||
if (!message.includes("CIAO ANNOUNCEMENT CANCELLED")) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
logDebug(
|
|
||||||
`bonjour: ignoring unhandled ciao rejection: ${formatBonjourError(reason)}`,
|
|
||||||
);
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import process from "node:process";
|
||||||
|
|
||||||
type UnhandledRejectionHandler = (reason: unknown) => boolean;
|
type UnhandledRejectionHandler = (reason: unknown) => boolean;
|
||||||
|
|
||||||
const handlers = new Set<UnhandledRejectionHandler>();
|
const handlers = new Set<UnhandledRejectionHandler>();
|
||||||
@@ -24,3 +26,14 @@ export function isUnhandledRejectionHandled(reason: unknown): boolean {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function installUnhandledRejectionHandler(): void {
|
||||||
|
process.on("unhandledRejection", (reason, _promise) => {
|
||||||
|
if (isUnhandledRejectionHandled(reason)) return;
|
||||||
|
console.error(
|
||||||
|
"[clawdbot] Unhandled promise rejection:",
|
||||||
|
reason instanceof Error ? (reason.stack ?? reason.message) : reason,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,21 +59,14 @@ async function main() {
|
|||||||
|
|
||||||
const { assertSupportedRuntime } = await import("../infra/runtime-guard.js");
|
const { assertSupportedRuntime } = await import("../infra/runtime-guard.js");
|
||||||
assertSupportedRuntime();
|
assertSupportedRuntime();
|
||||||
const { isUnhandledRejectionHandled } = await import(
|
const { installUnhandledRejectionHandler } = await import(
|
||||||
"../infra/unhandled-rejections.js"
|
"../infra/unhandled-rejections.js"
|
||||||
);
|
);
|
||||||
|
|
||||||
const { buildProgram } = await import("../cli/program.js");
|
const { buildProgram } = await import("../cli/program.js");
|
||||||
const program = buildProgram();
|
const program = buildProgram();
|
||||||
|
|
||||||
process.on("unhandledRejection", (reason, _promise) => {
|
installUnhandledRejectionHandler();
|
||||||
if (isUnhandledRejectionHandled(reason)) return;
|
|
||||||
console.error(
|
|
||||||
"[clawdbot] Unhandled promise rejection:",
|
|
||||||
reason instanceof Error ? (reason.stack ?? reason.message) : reason,
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on("uncaughtException", (error) => {
|
process.on("uncaughtException", (error) => {
|
||||||
console.error(
|
console.error(
|
||||||
|
|||||||
Reference in New Issue
Block a user