Skip to content

Commit c0d4abc

Browse files
committed
fix(gateway): suppress ciao interface assertions
Closes openclaw#38628. Refs openclaw#47159, openclaw#52431. Co-authored-by: Peter Steinberger <[email protected]>
1 parent 3faaf89 commit c0d4abc

File tree

4 files changed

+48
-16
lines changed

4 files changed

+48
-16
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ Docs: https://docs.openclaw.ai
257257
<<<<<<< HEAD
258258
- WhatsApp/reconnect: preserve the last inbound timestamp across reconnect attempts so the watchdog can still recycle linked-but-dead listeners after a restart instead of leaving them stuck connected forever.
259259
- Gateway/network discovery: guard LAN, tailnet, and pairing interface enumeration so WSL2 and restricted hosts degrade to missing-address fallbacks instead of crashing on `uv_interface_addresses` errors. (#44180, #47590)
260+
- Gateway/bonjour: suppress the non-fatal `@homebridge/ciao` IPv4-loss assertion during interface churn so WiFi/VPN/sleep-wake changes no longer take down the gateway. (#38628, #47159, #52431)
260261

261262
### Breaking
262263

src/infra/bonjour-ciao.test.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,70 @@
11
import { describe, expect, it, vi } from "vitest";
22

33
const logDebugMock = vi.hoisted(() => vi.fn());
4+
const logWarnMock = vi.hoisted(() => vi.fn());
45

56
vi.mock("../logger.js", () => ({
67
logDebug: (...args: unknown[]) => logDebugMock(...args),
8+
logWarn: (...args: unknown[]) => logWarnMock(...args),
79
}));
810

9-
const { ignoreCiaoCancellationRejection } = await import("./bonjour-ciao.js");
11+
const { ignoreCiaoUnhandledRejection } = await import("./bonjour-ciao.js");
1012

1113
describe("bonjour-ciao", () => {
1214
it("ignores and logs ciao announcement cancellation rejections", () => {
13-
expect(
14-
ignoreCiaoCancellationRejection(new Error("Ciao announcement cancelled by shutdown")),
15-
).toBe(true);
15+
expect(ignoreCiaoUnhandledRejection(new Error("Ciao announcement cancelled by shutdown"))).toBe(
16+
true,
17+
);
1618
expect(logDebugMock).toHaveBeenCalledWith(
1719
expect.stringContaining("ignoring unhandled ciao rejection"),
1820
);
21+
expect(logWarnMock).not.toHaveBeenCalled();
1922
});
2023

2124
it("ignores and logs ciao probing cancellation rejections", () => {
2225
logDebugMock.mockReset();
26+
logWarnMock.mockReset();
2327

24-
expect(ignoreCiaoCancellationRejection(new Error("CIAO PROBING CANCELLED"))).toBe(true);
28+
expect(ignoreCiaoUnhandledRejection(new Error("CIAO PROBING CANCELLED"))).toBe(true);
2529
expect(logDebugMock).toHaveBeenCalledWith(
2630
expect.stringContaining("ignoring unhandled ciao rejection"),
2731
);
32+
expect(logWarnMock).not.toHaveBeenCalled();
2833
});
2934

3035
it("ignores lower-case string cancellation reasons too", () => {
3136
logDebugMock.mockReset();
37+
logWarnMock.mockReset();
3238

33-
expect(ignoreCiaoCancellationRejection("ciao announcement cancelled during cleanup")).toBe(
34-
true,
35-
);
39+
expect(ignoreCiaoUnhandledRejection("ciao announcement cancelled during cleanup")).toBe(true);
3640
expect(logDebugMock).toHaveBeenCalledWith(
3741
expect.stringContaining("ignoring unhandled ciao rejection"),
3842
);
43+
expect(logWarnMock).not.toHaveBeenCalled();
44+
});
45+
46+
it("suppresses ciao interface assertion rejections as non-fatal", () => {
47+
logDebugMock.mockReset();
48+
logWarnMock.mockReset();
49+
50+
const error = Object.assign(
51+
new Error("Reached illegal state! IPV4 address change from defined to undefined!"),
52+
{ name: "AssertionError" },
53+
);
54+
55+
expect(ignoreCiaoUnhandledRejection(error)).toBe(true);
56+
expect(logWarnMock).toHaveBeenCalledWith(
57+
expect.stringContaining("suppressing ciao interface assertion"),
58+
);
59+
expect(logDebugMock).not.toHaveBeenCalled();
3960
});
4061

4162
it("keeps unrelated rejections visible", () => {
4263
logDebugMock.mockReset();
64+
logWarnMock.mockReset();
4365

44-
expect(ignoreCiaoCancellationRejection(new Error("boom"))).toBe(false);
66+
expect(ignoreCiaoUnhandledRejection(new Error("boom"))).toBe(false);
4567
expect(logDebugMock).not.toHaveBeenCalled();
68+
expect(logWarnMock).not.toHaveBeenCalled();
4669
});
4770
});

src/infra/bonjour-ciao.ts

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,21 @@
1-
import { logDebug } from "../logger.js";
1+
import { logDebug, logWarn } from "../logger.js";
22
import { formatBonjourError } from "./bonjour-errors.js";
33

44
const CIAO_CANCELLATION_MESSAGE_RE = /^CIAO (?:ANNOUNCEMENT|PROBING) CANCELLED\b/u;
5+
const CIAO_INTERFACE_ASSERTION_MESSAGE_RE =
6+
/REACHED ILLEGAL STATE!?\s+IPV4 ADDRESS CHANGE FROM DEFINED TO UNDEFINED!?/u;
57

6-
export function ignoreCiaoCancellationRejection(reason: unknown): boolean {
7-
const message = formatBonjourError(reason).toUpperCase();
8+
export function ignoreCiaoUnhandledRejection(reason: unknown): boolean {
9+
const formatted = formatBonjourError(reason);
10+
const message = formatted.toUpperCase();
811
if (!CIAO_CANCELLATION_MESSAGE_RE.test(message)) {
9-
return false;
12+
if (!CIAO_INTERFACE_ASSERTION_MESSAGE_RE.test(message)) {
13+
return false;
14+
}
15+
16+
logWarn(`bonjour: suppressing ciao interface assertion: ${formatted}`);
17+
return true;
1018
}
11-
logDebug(`bonjour: ignoring unhandled ciao rejection: ${formatBonjourError(reason)}`);
19+
logDebug(`bonjour: ignoring unhandled ciao rejection: ${formatted}`);
1220
return true;
1321
}

src/infra/bonjour.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { logDebug, logWarn } from "../logger.js";
22
import { getLogger } from "../logging.js";
3-
import { ignoreCiaoCancellationRejection } from "./bonjour-ciao.js";
3+
import { ignoreCiaoUnhandledRejection } from "./bonjour-ciao.js";
44
import { formatBonjourError } from "./bonjour-errors.js";
55
import { isTruthyEnvValue } from "./env.js";
66
import { registerUnhandledRejectionHandler } from "./unhandled-rejections.js";
@@ -175,7 +175,7 @@ export async function startGatewayBonjourAdvertiser(
175175

176176
const cleanupUnhandledRejection =
177177
services.length > 0
178-
? registerUnhandledRejectionHandler(ignoreCiaoCancellationRejection)
178+
? registerUnhandledRejectionHandler(ignoreCiaoUnhandledRejection)
179179
: undefined;
180180

181181
return { responder, services, cleanupUnhandledRejection };

0 commit comments

Comments
 (0)