¶ Boot loses sockets
apps/baileys-connector/src/index.ts starts the BullMQ worker and health server, but it does not reconnect channels that were already connected before the process restarted.
Architecture review
A production-safe design for making the Baileys connector self-healing after deploys, crashes, platform reschedules, and transient WhatsApp socket drops.
docs/connector-reconnection-design.md
Proposal: no code written yet
Single connector instance only
apps/baileys-connector/src/index.ts starts the BullMQ worker and health server, but it does not reconnect channels that were already connected before the process restarted.
The connection.update close handler in manager.ts:665-717 only reconnects immediately for restartRequired 515. Network blips, WhatsApp hiccups, idle drops, and other non-401 closes emit disconnected and stop.
In DigitalOcean App Platform the connector component is pinned to instance_count = 1, sessions live in Spaces, and every connector deploy restarts the process. Manual reconnects are masking a release-time outage.
Goals and boundaries
Channels that were connected or connecting rebuild Baileys sockets from the encrypted Spaces session without a QR code or human step.
Non-401, non-user-triggered closes schedule jittered retries instead of leaving a channel offline indefinitely.
No multi-instance scaling, no messaging-volume changes, no pairing-code changes, and no Cloud API replacement in this design.
Boot reconciliation
GET /internal/v1/channels/active
Trust boundaryHMAC-protected, same signed GET pattern as /{channel_id}/exists.
Returned setOnly channels with status IN ('connected', 'connecting').
Excluded setlogged_out, failed, disconnected, and awaiting_qr.
Add getActiveChannels(): Promise<string[]> to api-client.ts, then call reconcileActiveChannels() from index.ts main() after the worker and health server are listening. Each channel schedules a staggered connectChannel(id).
Transient auto-reconnect
scheduleReconnect(channelId, attempt).
BackoffExponential backoff with full jitter: base * 2^attempt, capped.
CancellationCancel on disconnect, logout, delete, or successful open.
Reconnect actionCall connectChannel(id), reusing the orphan guard and already-active no-op.
disconnectChannel must set an intentional-down marker before sock.end(), so the close handler can emit disconnected without scheduling a retry for user-requested disconnects.
Close handler decision table
401 loggedOut
Destroy session and emit logged_out. No retry.
515
Immediate reconnect, unchanged from today.
disconnected. No retry.
disconnected and schedule jittered backoff reconnect.
Hard constraints
Reconnect storms and double sockets are the connection-level ban signals. The design is acceptable only while these rules stay intact.
loggedOut 401.
2Use exponential backoff plus full jitter. Proposed: base 5s, factor 2, cap 5 min.
3Stop after a max attempts cap. Proposed: 8 attempts.
4Stagger boot reconciliation with 1-3s jitter between channels.
5Keep instance_count = 1. Scaling needs channel-to-instance assignment.
6Respect user intent: never auto-reconnect disconnected, logged_out, or failed.
Backoff base, cap, attempts, and boot stagger should be config constants with environment overrides so production can be dialed down without a code redeploy.
Failure modes
Skip reconciliation, log the error, and keep the worker serving jobs. Recovery can happen on the next deploy or manual reconnect.
connectChannel no-ops if active and the orphan guard catches deleted channels.
Pending delete or disconnect jobs cancel timers; any remaining attempt is stopped by the existing orphan check.
Backoff widens, attempts cap, and the channel eventually stays disconnected instead of retrying forever.
Not solved here. Boot reconciliation should recover from a hard kill, while shutdown batching remains separate work.
Testing plan
open using fake timers.
API pytest/internal/v1/channels/active returns only connected and connecting, requires HMAC, and handles empty lists.
Manual boot testPair, kill connector, restart, and confirm the channel reconnects with no QR.
Manual transient testDrop server network briefly and confirm backoff recovery without a storm.
Manual logout testTrigger a real 401 and confirm logged_out with no reconnect attempts.
Rollout
RECONNECT_ENABLED defaulting off.
Enable boot reconciliation and transient reconnect independently.
Validate on a throwaway number through real deploys before tenant numbers.
Review questions
awaiting_qr ever resume at boot, or stay user-driven?
Do we need a boot-time concurrency ceiling in addition to stagger?
Is one small /active list enough, or should pagination exist from day one?