#1651·OpenWA

[Bug]: Baileys ignores maxReconnectAttempts: a session set to 0 retries a failing connection forever

Author: rmyndharisCreated Sep 18, 2026Updated Sep 18, 2026

maxReconnectAttempts is documented as a cap on consecutive reconnects, with 0 disabling reconnect entirely, and no engine is excluded. On Baileys it has no effect on a connection that keeps failing: the engine retries without limit, the session stays initializing, and it never reaches failed. reconnectBaseDelay is ignored on the same path.

The contract is stated without an engine qualifier in docs/05-database-design.md:378, docs/06-api-specification.md:298-301 and :327, docs/12-troubleshooting-faq.md:593-596, the maxReconnectAttempts and reconnectBaseDelay descriptions in src/modules/session/dto/session-config.dto.ts (request and response DTOs) and src/modules/session/dto/create-session.dto.ts, openapi.json, and the type comments of all five SDKs. CHANGELOG.md states the behavior change the same way: "A session with an explicit maxReconnectAttempts now stops in failed once those attempts run out during an outage." GET /api/sessions/{id}/config also reports maxReconnectAttempts: 0 back as the effective value.

In the same sentence of create-session.dto.ts:19-28, autoRejectCalls is marked "Baileys engine only" and the two reconnect keys are not, so the asymmetry is not an oversight of convention.

Steps to reproduce

  1. Run OpenWA with ENGINE_TYPE=baileys.
  2. Create a session with an unreachable proxy and reconnect disabled: POST /api/sessions with {"name": "x", "proxyUrl": "http://127.0.0.1:9", "config": {"maxReconnectAttempts": 0}}.
  3. POST /api/sessions/{id}/start, then watch the logs and GET /api/sessions/{id}.

Expected

Either the documented behavior (the failing connection is not retried and the session ends in failed), or a documented contract that says what actually happens on this engine.

Actual

Baileys connection dropped; reconnecting with statusCode: 408 is logged every few seconds, followed by Session is reconnect-looping: attempt 5 scheduled. The session stays initializing and never reaches failed.

Root cause

Only the service-level reconnect reads the two keys, and Baileys does not use that path for transient closes.

  1. start() resolves the keys into the service's reconnect state (src/modules/session/session-engine-controls.ts:206-209), which only scheduleReconnect and decideReconnect read (src/modules/session/session-engine-lifecycle.service.ts:984-1076, src/modules/session/reconnect-policy.ts:73-104).
  2. That path starts from the engine's onDisconnected callback (src/modules/session/session-engine-event-wiring.ts:328-336) and from the liveness watchdog (src/modules/session/session-liveness-watchdog.service.ts:178). The watchdog only probes a READY engine (:91-100) and a Baileys reconnect episode holds the engine at INITIALIZING, so it never reaches a session that is looping.
  3. The Baileys adapter calls onDisconnected only after a logged-out (401) close (src/engine/adapters/baileys-lifecycle.ts:531-537), and even there only if the credential wipe succeeds; a failed wipe reports onError instead. Every other close is handled by the engine itself. A refused proxy connection arrives as a 408, because Baileys maps a WebSocket error with an E* code to 408.
  4. The engine's own scheduleReconnect has no attempt ceiling, uses a fixed 1 s base doubling to a 60 s cap, and reschedules itself after a failed attempt. It also resets its counter on any close more than 5 minutes after the previous one, so a finite cap would be unreachable in a slow-flapping outage even if one were passed in.
  5. The session config never reaches the adapter: BaileysAdapterConfig has no reconnect fields (src/engine/types/baileys.types.ts:28-42), and the wiring's onReconnecting counts attempts and raises the loop alert but never checks the cap (session-engine-event-wiring.ts:289-327).

whatsapp-web.js has no internal reconnect loop at all: every drop exits to the service through onDisconnected, so the cap binds there. The asymmetry is real, not a difference in wording.

The unbounded loop is deliberate

src/engine/adapters/baileys.adapter.spec.ts:901-955 pins it, as "unlimited retry: closes beyond the old 5-attempt cap keep reconnecting, backoff capped at 60 s", asserting no FAILED and no terminal onError. This is a conflict between the documented contract and a deliberate engine behavior, not a missing check.

Two ways out

Qualify the contract. Say what is already true: the two keys govern the service-level reconnect, which is every reconnect on whatsapp-web.js and the logged-out path on Baileys, while Baileys retries a transient close internally with its own 1 s to 60 s backoff and no ceiling. This touches the docs rows, the four DTO description strings, the regenerated openapi.json, the SDK type comments, the changelog note, and belongs in docs/29-engine-capability-matrix.md, which says nothing about reconnect today. No spec changes.

Enforce the cap on Baileys. Pass the resolved cap in BaileysAdapterConfig and end the episode before arming the timer, the way the 440 and 403 branches already do. Two hazards decide whether this is safe, and both can kill a healthy session:

  • The restart WhatsApp requires right after pairing arrives as a 515 close and is currently counted as attempt 1, so a naive cap of 0 would end every newly linked session immediately after a successful scan.
  • A transient 408 or 503 on a READY session is the common case, so counting closes rather than failed reconnect attempts would make the first blip on a healthy session terminal. That is the failure shape of #1501.

It also reverses a pinned hardening, threads two fields through a config surface documented as read by out-of-tree engine plugins, and parks a wrongly capped session in failed, which takeover deliberately does not adopt.

Qualifying the contract is the smaller and safer of the two, and is what this issue proposes unless there is a use case that needs the cap enforced on Baileys.

Separate from either: POST /sessions and PATCH /config accept, persist, validate and report maxReconnectAttempts: 0 on Baileys while nothing acts on it. Whether to reject it on that engine or surface it as inert in the response is a self-contained API-honesty question and should be decided on its own.

Environment

  • OpenWA built from main at cd6761b4; code references checked against main at 62dbe393
  • Engine: Baileys (@whiskeysockets/baileys 7.0.0-rc14)
  • Session proxy http://127.0.0.1:9, session config {"maxReconnectAttempts": 0}