openclaw - 💡(How to fix) Fix [v2026.4.24] Bonjour plugin keeps gateway in a permanent crash-restart loop because the unhandled-rejection handler is never wired [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#72230Fetched 2026-04-27 05:32:57
View on GitHub
Comments
1
Participants
2
Timeline
3
Reactions
1
Author
Timeline (top)
closed ×1commented ×1subscribed ×1

In v2026.4.24, the bonjour plugin defines a CIAO unhandled-rejection handler but never registers it with the global handler chain, because the plugin's entry point does not pass registerUnhandledRejectionHandler in deps.

When mDNS announce gets stuck for any reason (in our case macOS mDNSResponder already owning UDP/5353), the watchdog cancels the cycle, ciao throws CIAO ANNOUNCEMENT CANCELLED, the rejection bubbles up unhandled, OpenClaw's global handler exits the process, launchd restarts it, repeat — the gateway is in a permanent crash-restart loop.

Root Cause

In dist/extensions/bonjour/index.js, createCycle() returns:

cleanupUnhandledRejection: services.length > 0 && deps.registerUnhandledRejectionHandler
    ? deps.registerUnhandledRejectionHandler(handleCiaoUnhandledRejection)
    : void 0

But the plugin entry only passes { logger: api.logger }:

return { stop: (await startGatewayBonjourAdvertiser({}, { logger: api.logger })).stop };

deps.registerUnhandledRejectionHandler is therefore undefined, the ternary always evaluates to void 0, the handler is never added to the global handlers Set, and installUnhandledRejectionHandler's fallback path (process.exit(1)) takes over.

Fix Action

Workaround

Setting OPENCLAW_DISABLE_BONJOUR=1 in the launchd plist (EnvironmentVariables) short-circuits isDisabledByEnv() and stabilizes the gateway, at the cost of losing LAN auto-discovery.

Code Example

[plugins] bonjour: watchdog detected non-announced service; attempting re-advertise (gateway fqdn=… state=probing)
[plugins] bonjour: restarting advertiser (service stuck in announcing for 12700ms …)
[openclaw] Unhandled promise rejection: CIAO ANNOUNCEMENT CANCELLED
[openclaw] wrote stability bundle:/unhandled_rejection.json

---

cleanupUnhandledRejection: services.length > 0 && deps.registerUnhandledRejectionHandler
    ? deps.registerUnhandledRejectionHandler(handleCiaoUnhandledRejection)
    : void 0

---

return { stop: (await startGatewayBonjourAdvertiser({}, { logger: api.logger })).stop };

---

return { stop: (await startGatewayBonjourAdvertiser({}, {
    logger: api.logger,
    registerUnhandledRejectionHandler: api.registerUnhandledRejectionHandler,
})).stop };
RAW_BUFFERClick to expand / collapse

Summary

In v2026.4.24, the bonjour plugin defines a CIAO unhandled-rejection handler but never registers it with the global handler chain, because the plugin's entry point does not pass registerUnhandledRejectionHandler in deps.

When mDNS announce gets stuck for any reason (in our case macOS mDNSResponder already owning UDP/5353), the watchdog cancels the cycle, ciao throws CIAO ANNOUNCEMENT CANCELLED, the rejection bubbles up unhandled, OpenClaw's global handler exits the process, launchd restarts it, repeat — the gateway is in a permanent crash-restart loop.

Environment

  • macOS 25.4.0 (arm64)
  • OpenClaw v2026.4.24 (homebrew)
  • Node v25.7.0

Symptoms

~/.openclaw/logs/gateway.err.log — every ~20s:

[plugins] bonjour: watchdog detected non-announced service; attempting re-advertise (gateway fqdn=… state=probing)
[plugins] bonjour: restarting advertiser (service stuck in announcing for 12700ms …)
[openclaw] Unhandled promise rejection: CIAO ANNOUNCEMENT CANCELLED
[openclaw] wrote stability bundle: …/unhandled_rejection.json

launchctl list ai.openclaw.gateway PID changes every ~20s — launchd kickstart loop.

Root cause

In dist/extensions/bonjour/index.js, createCycle() returns:

cleanupUnhandledRejection: services.length > 0 && deps.registerUnhandledRejectionHandler
    ? deps.registerUnhandledRejectionHandler(handleCiaoUnhandledRejection)
    : void 0

But the plugin entry only passes { logger: api.logger }:

return { stop: (await startGatewayBonjourAdvertiser({}, { logger: api.logger })).stop };

deps.registerUnhandledRejectionHandler is therefore undefined, the ternary always evaluates to void 0, the handler is never added to the global handlers Set, and installUnhandledRejectionHandler's fallback path (process.exit(1)) takes over.

Suggested fix

(a) Expose registerUnhandledRejectionHandler on PluginAPI and pass it through deps:

return { stop: (await startGatewayBonjourAdvertiser({}, {
    logger: api.logger,
    registerUnhandledRejectionHandler: api.registerUnhandledRejectionHandler,
})).stop };

(b) Additionally, consider hoisting the registration to function scope so it lives across createCycle / stopCycle cycles. The current per-cycle register/unregister pattern has a race: stopCycle's finally { cleanupUnhandledRejection() } deregisters the handler in the same microtask tick that ciao's cancellation rejection is queued — by the time the rejection reaches the event loop, the handler is gone again, and the global fallback fires.

Workaround

Setting OPENCLAW_DISABLE_BONJOUR=1 in the launchd plist (EnvironmentVariables) short-circuits isDisabledByEnv() and stabilizes the gateway, at the cost of losing LAN auto-discovery.

extent analysis

TL;DR

The most likely fix is to expose registerUnhandledRejectionHandler on PluginAPI and pass it through deps to register the unhandled rejection handler.

Guidance

  • Verify that the registerUnhandledRejectionHandler function is being called by checking if it's being passed through deps in the plugin entry point.
  • Update the plugin entry point to pass registerUnhandledRejectionHandler through deps, as shown in the suggested fix.
  • Consider hoisting the registration to function scope to avoid the race condition between stopCycle and the event loop.
  • As a temporary workaround, setting OPENCLAW_DISABLE_BONJOUR=1 in the launchd plist can stabilize the gateway, but this will disable LAN auto-discovery.

Example

return { stop: (await startGatewayBonjourAdvertiser({}, {
    logger: api.logger,
    registerUnhandledRejectionHandler: api.registerUnhandledRejectionHandler,
})).stop };

Notes

The suggested fix assumes that registerUnhandledRejectionHandler is a valid function on PluginAPI. If this is not the case, further investigation is needed to determine the correct way to register the unhandled rejection handler.

Recommendation

Apply the suggested fix by exposing registerUnhandledRejectionHandler on PluginAPI and passing it through deps, as this should resolve the issue without disabling LAN auto-discovery.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix [v2026.4.24] Bonjour plugin keeps gateway in a permanent crash-restart loop because the unhandled-rejection handler is never wired [1 comments, 2 participants]