fix(whatsapp): reset watchdog timeout after reconnect (#60007)

Merged via squash.

Prepared head SHA: 64223e5dd4
Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
This commit is contained in:
Ted Li 2026-04-03 16:23:36 -07:00 committed by GitHub
parent 306fe841f5
commit ff62705206
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 14 additions and 10 deletions

View File

@ -96,6 +96,7 @@ Docs: https://docs.openclaw.ai
- Plugins/runtime: honor explicit capability allowlists during fallback speech, media-understanding, and image-generation provider loading so bundled capability plugins do not bypass restrictive `plugins.allow` config. (#52262) Thanks @PerfectPan.
- Hooks/tool policy: block tool calls when a `before_tool_call` hook crashes so hook failures fail closed instead of silently allowing execution. (#59822) Thanks @pgondhi987.
- Matrix/media: surface a dedicated `[matrix <kind> attachment too large]` marker for oversized inbound media instead of the generic unavailable marker, and classify size-limit failures with a typed Matrix error. (#60289) Thanks @efe-arv.
- WhatsApp/watchdog: reset watchdog timeout after reconnect so quiet channels no longer enter a tight reconnect loop from stale message timestamps carried across connection runs. (#60007) Thanks @MonkeyLeeT.
## 2026.4.2

View File

@ -188,7 +188,7 @@ describe("web auto-reply connection", () => {
}
});
it("keeps watchdog message age across reconnects", async () => {
it("gives a reconnected listener a fresh watchdog window", async () => {
vi.useFakeTimers();
try {
const { scripted, controller, run } = await startWatchdogScenario({
@ -203,7 +203,11 @@ describe("web auto-reply connection", () => {
{ timeout: 250, interval: 2 },
);
await vi.advanceTimersByTimeAsync(200);
await vi.advanceTimersByTimeAsync(20);
await Promise.resolve();
expect(scripted.getListenerCount()).toBe(2);
await vi.advanceTimersByTimeAsync(20);
await Promise.resolve();
await vi.waitFor(
() => {

View File

@ -50,13 +50,13 @@ type ActiveConnectionRun = {
backgroundTasks: Set<Promise<unknown>>;
};
function createActiveConnectionRun(lastInboundAt: number | null): ActiveConnectionRun {
function createActiveConnectionRun(): ActiveConnectionRun {
return {
connectionId: newConnectionId(),
startedAt: Date.now(),
heartbeat: null,
watchdogTimer: null,
lastInboundAt,
lastInboundAt: null,
handledMessages: 0,
unregisterUnhandled: null,
backgroundTasks: new Set<Promise<unknown>>(),
@ -171,7 +171,7 @@ export async function monitorWebChannel(
break;
}
const active = createActiveConnectionRun(status.lastInboundAt ?? status.lastMessageAt ?? null);
const active = createActiveConnectionRun();
// Watchdog to detect stuck message processing (e.g., event emitter died).
// Tuning overrides are test-oriented; production defaults remain unchanged.
@ -306,10 +306,9 @@ export async function monitorWebChannel(
}, heartbeatSeconds * 1000);
active.watchdogTimer = setInterval(() => {
if (!active.lastInboundAt) {
return;
}
const timeSinceLastMessage = Date.now() - active.lastInboundAt;
// A reconnect should get a fresh watchdog window even before the next inbound arrives.
const watchdogBaselineAt = active.lastInboundAt ?? active.startedAt;
const timeSinceLastMessage = Date.now() - watchdogBaselineAt;
if (timeSinceLastMessage <= MESSAGE_TIMEOUT_MS) {
return;
}
@ -319,7 +318,7 @@ export async function monitorWebChannel(
{
connectionId: active.connectionId,
minutesSinceLastMessage,
lastInboundAt: new Date(active.lastInboundAt),
lastInboundAt: active.lastInboundAt ? new Date(active.lastInboundAt) : null,
messagesHandled: active.handledMessages,
},
"Message timeout detected - forcing reconnect",