From 2f70888a77628fe6a04778270d414edbd91c84ad Mon Sep 17 00:00:00 2001 From: AyedAlmudarra Date: Fri, 30 Jan 2026 15:44:53 +0300 Subject: [PATCH] feat(gateway): add integration test and changelog for mDNS fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add bonjour-errors.integration.test.ts with 8 tests - Update CHANGELOG.md with unreleased section documenting fixes - Fix MDNS → MDNSSERVER pattern in bonjour-ciao.ts Completes recommendations for #3821, #3815, #4501 --- CHANGELOG.md | 10 +++ src/infra/bonjour-ciao.ts | 3 +- src/infra/bonjour-errors.integration.test.ts | 94 ++++++++++++++++++++ 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/infra/bonjour-errors.integration.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c0549c16..c09867c6d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ Docs: https://docs.openclaw.ai +## Unreleased + +### Fixes +- Gateway: fix crash resilience for mDNS/Bonjour errors during network interface changes (sleep/wake, WiFi reconnect). (#3821) + - Added error patterns for "IPv4 address changed" and "illegal state" assertions from @homebridge/ciao. + - Added `--no-bonjour` CLI flag to disable mDNS advertising entirely. +- Gateway: improve handling of transient network errors to prevent crashes. (#3815, #4501) + - Added ERR_ASSERTION to transient network error codes. + - Better detection of mDNS-related assertion errors. + ## 2026.1.29 Status: stable. diff --git a/src/infra/bonjour-ciao.ts b/src/infra/bonjour-ciao.ts index 401669df1..6bd99d9f1 100644 --- a/src/infra/bonjour-ciao.ts +++ b/src/infra/bonjour-ciao.ts @@ -21,7 +21,8 @@ export function ignoreCiaoCancellationRejection(reason: unknown): boolean { const isTransientError = BONJOUR_TRANSIENT_ERRORS.some((pattern) => message.includes(pattern)); // Also catch AssertionError from MDNServer (common during network changes) - const isAssertionError = errorName === "ASSERTIONERROR" && message.includes("MDNS"); + // Note: The error message typically contains "MDNSServer" in the stack trace + const isAssertionError = errorName === "ASSERTIONERROR" && message.includes("MDNSSERVER"); if (!isTransientError && !isAssertionError) { return false; diff --git a/src/infra/bonjour-errors.integration.test.ts b/src/infra/bonjour-errors.integration.test.ts new file mode 100644 index 000000000..8deed284e --- /dev/null +++ b/src/infra/bonjour-errors.integration.test.ts @@ -0,0 +1,94 @@ +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import process from "node:process"; + +import { + installUnhandledRejectionHandler, + isTransientNetworkError, + isAbortError, +} from "./unhandled-rejections.js"; +import { ignoreCiaoCancellationRejection } from "./bonjour-ciao.js"; +import { registerUnhandledRejectionHandler } from "./unhandled-rejections.js"; + +describe("mDNS error handling integration", () => { + let originalExit: typeof process.exit; + + beforeAll(() => { + originalExit = process.exit.bind(process); + installUnhandledRejectionHandler(); + registerUnhandledRejectionHandler(ignoreCiaoCancellationRejection); + }); + + afterAll(() => { + process.exit = originalExit; + }); + + describe("error detection functions", () => { + it("detects IPv4 address change as transient network error", () => { + const mdnsError = Object.assign( + new Error("Reached illegal state! IPv4 address changed from undefined to defined!"), + { + name: "AssertionError", + code: "ERR_ASSERTION", + }, + ); + + expect(isTransientNetworkError(mdnsError)).toBe(true); + }); + + it("detects MDNSServer illegal state as transient network error", () => { + const mdnsError = new Error("MDNSServer: Reached illegal state during network update"); + + expect(isTransientNetworkError(mdnsError)).toBe(true); + }); + + it("detects AbortError correctly", () => { + const abortError = Object.assign(new Error("This operation was aborted"), { + name: "AbortError", + }); + + expect(isAbortError(abortError)).toBe(true); + }); + + it("does not treat fatal errors as transient", () => { + const fatalError = Object.assign(new Error("Out of memory"), { + code: "ERR_OUT_OF_MEMORY", + }); + + expect(isTransientNetworkError(fatalError)).toBe(false); + }); + }); + + describe("ciao cancellation handler", () => { + it("handles CIAO announcement cancelled error", () => { + const ciaoError = new Error("CIAO announcement cancelled due to network change"); + + expect(ignoreCiaoCancellationRejection(ciaoError)).toBe(true); + }); + + it("handles IPv4 address change error", () => { + const mdnsError = Object.assign( + new Error("Reached illegal state! IPv4 address changed from undefined to defined!"), + { + name: "AssertionError", + code: "ERR_ASSERTION", + }, + ); + + expect(ignoreCiaoCancellationRejection(mdnsError)).toBe(true); + }); + + it("handles MDNSServer illegal state error", () => { + const mdnsError = Object.assign(new Error("MDNSServer: Reached illegal state"), { + name: "AssertionError", + }); + + expect(ignoreCiaoCancellationRejection(mdnsError)).toBe(true); + }); + + it("does not handle unrelated errors", () => { + const genericError = new Error("Something went wrong"); + + expect(ignoreCiaoCancellationRejection(genericError)).toBe(false); + }); + }); +});