deps: bump whatsmeow to fix stream-error reconnect loop that stalls offline queue delivery - #190
Conversation
…ect loop The pinned whatsmeow (20260630-b572e5bc) predates three upstream fixes that together cause a linked device to flap and stop draining its offline queue: 28bfe53 (2026-09-04) client: ensure stream error is handled before reconnecting 0fadda7 (2026-08-28) client,socket: use correct context for noise socket frames 4fa3462 (2026-08-21) client: don't reuse handler queue between connections Observed on 0.7.2 in production: every ~50 min the server ends the stream with <stream:error><ack class="status" id="3EB0..." type="media"/></stream:error> followed by a websocket EOF and a reconnect. Over 30 h that produced 34 stream errors and 38 re-authentications on a single instance. The client keeps authenticating, but OfflineSyncPreview only grows (Messages 738 -> 923) and no message is ever delivered, so live capture stops entirely. Same signature as evolution-foundation#185. whatsmeow >= 20260904 requires Go 1.26, so the go directive and the builder image move with it. The only source change needed is SetStatusMessage, which now takes a structured types.SetStatusInput instead of a bare string. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013rUP8Un1mWQdfRg1cMSc73
Reviewer's guide (collapsed on small PRs)Reviewer's GuideThis PR upgrades whatsmeow and the Go toolchain to pull in upstream fixes for stream-error handling, socket context usage, and per-connection handler queues, adapts the status-message API, and reports production evidence that reconnect loops and offline-queue stalls stopped; reviewers should also assess the broad transitive dependency refresh and the unresolved PostgreSQL connection-leak failure mode. Sequence diagram for reconnect and offline queue recoverysequenceDiagram
participant Whatsmeow
participant Client
participant HandlerQueue
participant OfflineQueue
Whatsmeow->>Client: stream error
Client->>Client: handle stream error
Client->>HandlerQueue: create queue for connection
Client->>Whatsmeow: reconnect
Whatsmeow-->>Client: OfflineSyncPreview
Client->>OfflineQueue: process queued events
OfflineQueue-->>Client: backlog drained
Client-->>Whatsmeow: continue live traffic
Sequence diagram for structured profile status updatesequenceDiagram
participant UserService
participant WhatsmeowClient
participant Whatsmeow
UserService->>WhatsmeowClient: SetStatusMessage(context, SetStatusInput)
WhatsmeowClient->>Whatsmeow: update status text
Whatsmeow-->>WhatsmeowClient: result
WhatsmeowClient-->>UserService: result
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've reviewed your changes and they look great!
Fixed security issues:
Sourcery assessment
Needs a human reviewer. The whatsmeow upgrade changes the runtime behavior of reconnecting and delivering queued outbound messages, so a regression could cause messages to be duplicated, misdelivered, or sent at the wrong time to external recipients. Reverting restores the previous client behavior, but any messages already sent cannot be recalled.
…onnect
`StartClient` called `sqlstore.New` on every (re)connection. Each call opens a
fresh `*sql.DB`, and Go's pool keeps its idle connections alive indefinitely.
The container was a local variable — never stored, never closed — so every
reconnect leaked a pool.
Measured on a production instance: the reconnect loop pushed `pg_stat_activity`
from 2 to 99 idle connections owned by containers that no longer existed. Once
`max_connections` (100) was exhausted the client could no longer even start:
Failed to create container: failed to upgrade database:
failed to check if version table is up to date: pq: sorry, too many clients already
At that point capture stops entirely — a flapping-but-alive client becomes a
dead one. Keepalive timeouts alone (normal on a long-lived socket) are enough to
reach this state given enough days.
The DSN is fixed for the process lifetime, and `sqlstore.Container` is safe for
concurrent use, so one shared container per DSN is both sufficient and correct.
A small mutex-guarded map keeps it lazy and keyed by dialect+address.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EXKS6ixSuQsCLfaF41oHKB
|
Pushed a second commit to this branch: The "secondary failure mode" I described under Testing turned out to be a bug in
container, err = sqlstore.New(context.Background(), "postgres", w.config.PostgresAuthDB, nil)
That is why the counts I reported climbed to 99 idle connections owned by Worth stressing: this does not need the stream-error bug to trigger. After The fix keeps one shared container per DSN behind a mutex-guarded map. The DSN is Deployed here: the instance reconnects normally and the pool sits at a flat 3 Happy to split this into its own PR if you would rather keep the dependency bump |
Problem
On 0.7.2 a linked device flaps every ~50 minutes and stops draining its offline
queue. The server ends the stream with a node whatsmeow doesn't recognise:
Over 30 h on a single instance: 34 stream errors, 38 re-authentications. The
idrotates every few hours (5 distinct ids observed) and never corresponds to areal message — it is not in the instance's message history.
The damaging part is not the flap itself but what it does to delivery.
OfflineSyncPreviewis announced on every reconnect and keeps growing, whilealmost nothing is delivered:
Net effect: a ~19 h window with zero messages captured, then one burst of
backlog, then silence again. Same error signature as #185 (that report ends in a
terminal dead client; here the client keeps reconnecting but never delivers).
Cause
go.modpinsgo.mau.fi/whatsmeow v0.0.0-20260630180629-b572e5bcb92b. Threeupstream fixes landed after that pin and map directly onto the symptoms:
28bfe530fadda74fa34624fa3462in particular explains the queue being announced but never processed:the handler queue was being reused across connections.
Change
go.mau.fi/whatsmeow→v0.0.0-20260904121843-28bfe537ea6agodirective and the builderimage in
Dockerfilemove to 1.26 (golang:1.26-alpine).SetStatusMessagenow takes a structuredtypes.SetStatusInputinstead of a barestring.Transitive bumps in
go.sumcome fromgo mod tidy.Testing
Built and deployed to a production single-node Swarm on 2026-09-08 12:57 UTC,
against the same paired instance that produced the report above.
CGO_ENABLED=1, alpine builder).go.mau.fi/whatsmeow v0.0.0-20260904121843-28bfe537ea6a.Same instance, same host, before and after:
Unknown stream errorDisconnectedOn reconnect the client reported an offline backlog of 14144 events
(3301 messages) accumulated over ~1.5 days of the loop. It drained completely
in 59 minutes — 3056 messages processed, rate falling from ~120/2min to 0 —
and stayed at live traffic afterwards. Under 0.7.2 the same backlog only grew.
A deliberate service restart mid-test (to apply an unrelated config change) was
also clean: the instance reconnected on its own and resumed processing 10 s after
boot, with no stream error.
Caveat on scope: this is ~1 hour of production uptime. That is past the ~50 min
interval at which the loop reliably reproduced under 0.7.2, but it is not a
multi-day soak. Reporting it as strong evidence, not proof.
Secondary failure mode worth noting
The reconnect loop leaked one PostgreSQL connection per cycle. After ~99 cycles
the instance had exhausted
max_connections(100) with idle connections owned bycontainers that no longer existed, and the client could no longer start at
all:
At that point the gateway had zero usable connections and capture stopped
entirely — which is how a flapping-but-alive client turns into the fully dead
client described in #185. Clearing the orphans was required before the new build
could start; afterwards connection count has stayed flat at 2-4 with no growth,
since there are no reconnect cycles to leak from.
I did not investigate where the connections are held open, so this part is a
report rather than a fix — it may be worth a separate look regardless of the
whatsmeow bump.
🤖 Generated with Claude Code
https://claude.ai/code/session_014UPPP6Bg3NnBcLfJFEGVw1
Summary by Sourcery
Upgrade the WhatsApp client dependency and database store lifecycle to keep reconnecting clients healthy and reliably process offline queues.
Bug Fixes:
Enhancements:
Build: