mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
fix(bonjour): keep ciao failure handling extension-owned
This commit is contained in:
@@ -48,6 +48,34 @@ describe("bonjour-ciao", () => {
|
||||
expect(ignoreCiaoUnhandledRejection(new Error("CIAO PROBING CANCELLED"))).toBe(true);
|
||||
});
|
||||
|
||||
it("suppresses wrapped ciao cancellation rejections", () => {
|
||||
expect(
|
||||
classifyCiaoUnhandledRejection({
|
||||
reason: new Error("CIAO ANNOUNCEMENT CANCELLED"),
|
||||
}),
|
||||
).toEqual({
|
||||
kind: "cancellation",
|
||||
formatted: "CIAO ANNOUNCEMENT CANCELLED",
|
||||
});
|
||||
});
|
||||
|
||||
it("suppresses aggregate ciao assertion rejections", () => {
|
||||
expect(
|
||||
classifyCiaoUnhandledRejection(
|
||||
new AggregateError([
|
||||
Object.assign(
|
||||
new Error("Reached illegal state! IPV4 address change from defined to undefined!"),
|
||||
{ name: "AssertionError" },
|
||||
),
|
||||
]),
|
||||
),
|
||||
).toEqual({
|
||||
kind: "interface-assertion",
|
||||
formatted:
|
||||
"AssertionError: Reached illegal state! IPV4 address change from defined to undefined!",
|
||||
});
|
||||
});
|
||||
|
||||
it("suppresses lower-case string cancellation reasons too", () => {
|
||||
expect(ignoreCiaoUnhandledRejection("ciao announcement cancelled during cleanup")).toBe(true);
|
||||
});
|
||||
|
||||
@@ -11,17 +11,59 @@ export type CiaoProcessErrorClassification =
|
||||
| { kind: "interface-assertion"; formatted: string }
|
||||
| { kind: "netmask-assertion"; formatted: string };
|
||||
|
||||
function collectCiaoProcessErrorCandidates(reason: unknown): unknown[] {
|
||||
const queue: unknown[] = [reason];
|
||||
const seen = new Set<unknown>();
|
||||
const candidates: unknown[] = [];
|
||||
|
||||
while (queue.length > 0) {
|
||||
const current = queue.shift();
|
||||
if (current == null || seen.has(current)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(current);
|
||||
candidates.push(current);
|
||||
|
||||
if (!current || typeof current !== "object") {
|
||||
continue;
|
||||
}
|
||||
const record = current as Record<string, unknown>;
|
||||
for (const nested of [
|
||||
record.cause,
|
||||
record.reason,
|
||||
record.original,
|
||||
record.error,
|
||||
record.data,
|
||||
]) {
|
||||
if (nested != null && !seen.has(nested)) {
|
||||
queue.push(nested);
|
||||
}
|
||||
}
|
||||
if (Array.isArray(record.errors)) {
|
||||
for (const nested of record.errors) {
|
||||
if (nested != null && !seen.has(nested)) {
|
||||
queue.push(nested);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return candidates;
|
||||
}
|
||||
|
||||
export function classifyCiaoProcessError(reason: unknown): CiaoProcessErrorClassification | null {
|
||||
const formatted = formatBonjourError(reason);
|
||||
const message = formatted.toUpperCase();
|
||||
if (CIAO_CANCELLATION_MESSAGE_RE.test(message)) {
|
||||
return { kind: "cancellation", formatted };
|
||||
}
|
||||
if (CIAO_INTERFACE_ASSERTION_MESSAGE_RE.test(message)) {
|
||||
return { kind: "interface-assertion", formatted };
|
||||
}
|
||||
if (CIAO_NETMASK_ASSERTION_MESSAGE_RE.test(message)) {
|
||||
return { kind: "netmask-assertion", formatted };
|
||||
for (const candidate of collectCiaoProcessErrorCandidates(reason)) {
|
||||
const formatted = formatBonjourError(candidate);
|
||||
const message = formatted.toUpperCase();
|
||||
if (CIAO_CANCELLATION_MESSAGE_RE.test(message)) {
|
||||
return { kind: "cancellation", formatted };
|
||||
}
|
||||
if (CIAO_INTERFACE_ASSERTION_MESSAGE_RE.test(message)) {
|
||||
return { kind: "interface-assertion", formatted };
|
||||
}
|
||||
if (CIAO_NETMASK_ASSERTION_MESSAGE_RE.test(message)) {
|
||||
return { kind: "netmask-assertion", formatted };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -196,32 +196,6 @@ describe("installUnhandledRejectionHandler - fatal detection", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("does not exit on known Bonjour advertiser failures", () => {
|
||||
const bonjourCases: unknown[] = [
|
||||
new Error("CIAO ANNOUNCEMENT CANCELLED"),
|
||||
new Error("CIAO PROBING CANCELLED"),
|
||||
Object.assign(
|
||||
new Error("Reached illegal state! IPV4 address change from defined to undefined!"),
|
||||
{ name: "AssertionError" },
|
||||
),
|
||||
Object.assign(
|
||||
new Error(
|
||||
"IP address version must match. Netmask cannot have a version different from the address!",
|
||||
),
|
||||
{ name: "AssertionError" },
|
||||
),
|
||||
];
|
||||
|
||||
for (const bonjourErr of bonjourCases) {
|
||||
expectExitCodeFromUnhandled(bonjourErr, []);
|
||||
}
|
||||
|
||||
expect(consoleWarnSpy).toHaveBeenCalledWith(
|
||||
"[openclaw] Non-fatal unhandled rejection (continuing):",
|
||||
expect.stringContaining("CIAO ANNOUNCEMENT CANCELLED"),
|
||||
);
|
||||
});
|
||||
|
||||
it("exits on generic errors without code", () => {
|
||||
const genericErr = new Error("Something went wrong");
|
||||
|
||||
|
||||
@@ -116,12 +116,6 @@ const TRANSIENT_SQLITE_MESSAGE_SNIPPETS = [
|
||||
"disk i/o error",
|
||||
];
|
||||
|
||||
const CIAO_CANCELLATION_MESSAGE_RE = /^CIAO (?:ANNOUNCEMENT|PROBING) CANCELLED\b/u;
|
||||
const CIAO_INTERFACE_ASSERTION_MESSAGE_RE =
|
||||
/REACHED ILLEGAL STATE!?\s+IPV4 ADDRESS CHANGE FROM (?:DEFINED TO UNDEFINED|UNDEFINED TO DEFINED)!?/u;
|
||||
const CIAO_NETMASK_ASSERTION_MESSAGE_RE =
|
||||
/IP ADDRESS VERSION MUST MATCH\.\s+NETMASK CANNOT HAVE A VERSION DIFFERENT FROM THE ADDRESS!?/u;
|
||||
|
||||
function hasSqliteSignal(err: unknown): boolean {
|
||||
if (!err || typeof err !== "object") {
|
||||
return false;
|
||||
@@ -341,46 +335,8 @@ export function isTransientSqliteError(err: unknown): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isNonFatalBonjourAdvertiserError(err: unknown): boolean {
|
||||
if (!err) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const candidate of collectNestedUnhandledErrorCandidates(err)) {
|
||||
const rawMessage =
|
||||
candidate && typeof candidate === "object"
|
||||
? (candidate as { message?: unknown }).message
|
||||
: undefined;
|
||||
const message =
|
||||
typeof candidate === "string"
|
||||
? candidate
|
||||
: candidate && typeof candidate === "object"
|
||||
? typeof rawMessage === "string"
|
||||
? rawMessage
|
||||
: ""
|
||||
: "";
|
||||
const normalized = message.trim().toUpperCase();
|
||||
if (!normalized) {
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
CIAO_CANCELLATION_MESSAGE_RE.test(normalized) ||
|
||||
CIAO_INTERFACE_ASSERTION_MESSAGE_RE.test(normalized) ||
|
||||
CIAO_NETMASK_ASSERTION_MESSAGE_RE.test(normalized)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function isTransientUnhandledRejectionError(err: unknown): boolean {
|
||||
return (
|
||||
isTransientNetworkError(err) ||
|
||||
isTransientSqliteError(err) ||
|
||||
isNonFatalBonjourAdvertiserError(err)
|
||||
);
|
||||
return isTransientNetworkError(err) || isTransientSqliteError(err);
|
||||
}
|
||||
|
||||
export function registerUnhandledRejectionHandler(handler: UnhandledRejectionHandler): () => void {
|
||||
|
||||
Reference in New Issue
Block a user