Skip to content

fix(whatsapp): stop reconnecting quiet sockets#72145

Merged
vincentkoc merged 2 commits intomainfrom
clownfish/ghcrawl-165984-agentic-merge
Apr 26, 2026
Merged

fix(whatsapp): stop reconnecting quiet sockets#72145
vincentkoc merged 2 commits intomainfrom
clownfish/ghcrawl-165984-agentic-merge

Conversation

@vincentkoc
Copy link
Copy Markdown
Member

Summary

Canonical issue

Fixes #70678.
Related: #53698, #65215, #71466, #63939.

Validation

  • pnpm check:changed

Notes

This replacement exists because #71466 is draft, dirty/unmergeable, has skipped validation checks, and cannot be safely updated automatically; #63939 is useful but does not fix the bad production default by itself.

ProjectClownfish replacement details:

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Apr 26, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🔵 Low Potential event-storm DoS via unthrottled WebSocket 'frame' listener
1. 🔵 Potential event-storm DoS via unthrottled WebSocket 'frame' listener
Property Value
Severity Low
CWE CWE-400
Location extensions/whatsapp/src/connection-controller.ts:614-622

Description

WhatsAppConnectionController attaches a listener to the underlying WebSocket transport's low-level frame event and updates state on every emitted frame.

  • sock.ws.on('frame', ...) can fire at a much higher rate than application-level message events (e.g., fragmented frames, pings/pongs, protocol acks).
  • Each frame triggers a JS callback and Date.now() call, creating avoidable CPU/GC overhead.
  • If an upstream peer (or an on-path attacker) can increase frame frequency, the process can experience resource exhaustion (event-loop saturation), degrading availability.

Vulnerable code:

const noteActivity = () => this.noteTransportActivity();
ws.on("frame", noteActivity);

Recommendation

Avoid per-frame listeners or throttle updates.

Options:

  1. Prefer higher-level, lower-frequency events (e.g., message, ping, pong, open, close) if available.

  2. Throttle noteTransportActivity to at most once per interval:

private attachTransportActivityListener(sock: WASocket): (() => void) | null {
  const ws = sock.ws as SocketActivityEmitter | undefined;
  if (!ws || typeof ws.on !== "function") return null;

  let lastNoted = 0;
  const minIntervalMs = 250; // tune as needed
  const noteActivity = () => {
    const now = Date.now();
    if (now - lastNoted < minIntervalMs) return;
    lastNoted = now;
    this.noteTransportActivity(now);
  };

  ws.on("frame", noteActivity);
  return () => {
    if (typeof ws.off === "function") ws.off("frame", noteActivity);
    else ws.removeListener?.("frame", noteActivity);
  };
}

This preserves the watchdog signal while limiting callback frequency under frame storms.


Analyzed PR: #72145 at commit efae4af

Last updated on: 2026-04-26T12:05:43Z

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: whatsapp-web Channel integration: whatsapp-web size: S maintainer Maintainer-authored PR labels Apr 26, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 26, 2026

Greptile Summary

This PR changes the WhatsApp watchdog timer to track WebSocket transport-frame activity (sock.ws "frame" events) instead of inbound app-message volume, preventing quiet but healthy linked-device sessions from being restarted every 30 minutes. The implementation is clean: a new lastTransportActivityAt field is initialized at connection creation time, updated on every inbound message and on every raw "frame" event, and used as the sole baseline in the watchdog interval. Cleanup is properly wired into closeCurrentConnection; a new e2e test validates the steady-state scenario.

Confidence Score: 4/5

Safe to merge; one minor cleanup gap in the error path.

Only a P2 finding: the catch block in connectSocket doesn't call connection.unregisterTransportActivity?.() after line 392. The omission is self-healing (a future reconnect calls closeCurrentConnection which does clean up), but it's inconsistent with the unregisterUnhandled pattern already present. No logic or security issues found.

extensions/whatsapp/src/connection-controller.ts — catch block cleanup around line 399–407.

Comments Outside Diff (1)

  1. extensions/whatsapp/src/connection-controller.ts, line 399-407 (link)

    P2 Missing unregisterTransportActivity in catch block

    attachTransportActivityListener is called at line 392, after this.current = connection is set, so if registerWhatsAppConnectionController or startTimers throws, the catch block runs but only tears down unregisterUnhandled — the frame-listener on sock.ws is left registered. The connection is self-healing (a subsequent connectSocket call will closeCurrentConnection which does call unregisterTransportActivity), but the teardown pattern already established for unregisterUnhandled should be applied here for consistency.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: extensions/whatsapp/src/connection-controller.ts
    Line: 399-407
    
    Comment:
    **Missing `unregisterTransportActivity` in catch block**
    
    `attachTransportActivityListener` is called at line 392, _after_ `this.current = connection` is set, so if `registerWhatsAppConnectionController` or `startTimers` throws, the catch block runs but only tears down `unregisterUnhandled` — the frame-listener on `sock.ws` is left registered. The connection is self-healing (a subsequent `connectSocket` call will `closeCurrentConnection` which does call `unregisterTransportActivity`), but the teardown pattern already established for `unregisterUnhandled` should be applied here for consistency.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/whatsapp/src/connection-controller.ts
Line: 399-407

Comment:
**Missing `unregisterTransportActivity` in catch block**

`attachTransportActivityListener` is called at line 392, _after_ `this.current = connection` is set, so if `registerWhatsAppConnectionController` or `startTimers` throws, the catch block runs but only tears down `unregisterUnhandled` — the frame-listener on `sock.ws` is left registered. The connection is self-healing (a subsequent `connectSocket` call will `closeCurrentConnection` which does call `unregisterTransportActivity`), but the teardown pattern already established for `unregisterUnhandled` should be applied here for consistency.

```suggestion
    } catch (err) {
      if (this.socketRef.current === sock) {
        this.socketRef.current = null;
      }
      closeWaSocket(sock);
      if (connection?.unregisterUnhandled) {
        connection.unregisterUnhandled();
      }
      connection?.unregisterTransportActivity?.();
      throw err;
    }
```

How can I resolve this? If you propose a fix, please make it concise.

Reviews (1): Last reviewed commit: "fix(whatsapp): stop quiet sessions from ..." | Re-trigger Greptile

@vincentkoc
Copy link
Copy Markdown
Member Author

ProjectClownfish follow-up addressed the bot review items in efae4af:

  • Aisle security concern: the watchdog now uses transport activity for quiet healthy sessions, but keeps a longer application-silence cap so raw frame activity cannot hide a stuck session forever.
  • Greptile cleanup concern: failed connection-open paths now unregister the transport activity listener alongside the existing unhandled cleanup.

Validation:

  • pnpm test:serial extensions/whatsapp/src/auto-reply.web-auto-reply.connection-and-logging.e2e.test.ts extensions/whatsapp/src/connection-controller.test.ts
  • pnpm check:changed
  • codex review --base origin/main passed with no actionable correctness findings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: whatsapp-web Channel integration: whatsapp-web docs Improvements or additions to documentation maintainer Maintainer-authored PR size: M

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: WhatsApp channel force-reconnects every 30 minutes on quiet-device sessions

1 participant