Skip to content

fix: trata DisconnectReason.connectionReplaced e evita reconexoes concorrentes#2655

Open
PhyBruno wants to merge 6 commits into
evolution-foundation:developfrom
PhyBruno:fix/problema-1-desconexao-diaria
Open

fix: trata DisconnectReason.connectionReplaced e evita reconexoes concorrentes#2655
PhyBruno wants to merge 6 commits into
evolution-foundation:developfrom
PhyBruno:fix/problema-1-desconexao-diaria

Conversation

@PhyBruno

@PhyBruno PhyBruno commented Jul 24, 2026

Copy link
Copy Markdown

Problema

Em producao, observamos uma instancia especifica desconectando praticamente todo dia, sem nenhuma acao do usuario do lado do dispositivo.

Causa raiz

Em whatsapp.baileys.service.ts, o handler de connection.update (connection === 'close') tem uma lista codesToNotReconnect que nao inclui DisconnectReason.connectionReplaced (440 - emitido pelo Baileys quando a mesma sessao e assumida em outro lugar, ou por uma conexao concorrente). Isso faz o codigo tentar reconectar automaticamente com credenciais que a propria troca de sessao ja invalidou, gerando um ciclo close -> reconnect -> close.

Agravante: connectToWhatsapp/createClient nao tem nenhum lock contra chamadas concorrentes. Se uma reconexao manual (ex. disparada por uma integracao externa) ocorrer enquanto a reconexao automatica do listener de close ja esta em andamento, dois sockets Baileys podem competir pela mesma sessao - um gatilho conhecido do proprio connectionReplaced.

Mudancas

  • connectionReplaced adicionado a codesToNotReconnect, com log explicito avisando que a sessao foi assumida em outro lugar e que sera necessario um novo QR Code (evita reconectar em loop com credenciais ja invalidadas).
  • Lock de instancia (isConnecting) em createClient, evitando que dois sockets sejam criados concorrentemente para a mesma instancia.

Validacao

  • npx tsc --noEmit: sem erros.
  • npx eslint --ext .ts src: sem erros/warnings.
  • Sem alteracao de dependencias.

Relacionado: #2 (mesmo metodo createClient, mudanca independente) e #3 (falhas relacionadas de QR/Chatwoot).

Summary by Sourcery

Handle Baileys connection replacement as a terminal state and prevent concurrent WhatsApp reconnection attempts for the same instance.

Bug Fixes:

  • Stop automatic reconnection loops when Baileys reports DisconnectReason.connectionReplaced for a WhatsApp session.
  • Avoid creating multiple concurrent Baileys sockets that race on the same session and can themselves trigger connection replacement disconnects.

Enhancements:

  • Add explicit logging when a WhatsApp session is replaced elsewhere, clarifying that a new QR scan or explicit reconnect is required.

DavidsonGomes and others added 6 commits May 6, 2026 13:58
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n-foundation

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…correntes

Instancias especificas desconectavam diariamente sem intervencao do
usuario. Causa raiz: DisconnectReason.connectionReplaced (440) nao
estava na lista de codigos que interrompem a reconexao automatica,
entao o socket tentava reconectar com credenciais ja invalidadas pela
troca de sessao, gerando um ciclo close -> reconnect -> close.

Alem disso, connectToWhatsapp/createClient nao tinham nenhum lock
contra chamadas concorrentes: se uma reconexao manual (ex. via
Chatwoot) disparasse enquanto a reconexao automatica do listener de
'close' ja estava em andamento, dois sockets Baileys podiam competir
pela mesma sessao - um dos gatilhos conhecidos do proprio
connectionReplaced.

- Adiciona connectionReplaced a lista de codigos que nao devem
  reconectar automaticamente, com log explicito exigindo novo QR.
- Adiciona lock de instancia (isConnecting) em createClient para
  impedir sockets concorrentes na mesma sessao.
@sourcery-ai

sourcery-ai Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Adds handling for Baileys DisconnectReason.connectionReplaced to avoid auto-reconnect loops and introduces an isConnecting guard in createClient to prevent concurrent socket connections on the same WhatsApp session.

Sequence diagram for updated disconnect handling with connectionReplaced

sequenceDiagram
  participant Baileys
  participant BaileysStartupService

  Baileys->>BaileysStartupService: connection.update
  alt [connection == close]
    BaileysStartupService->>BaileysStartupService: determine statusCode
    alt [statusCode == DisconnectReason.connectionReplaced]
      BaileysStartupService->>BaileysStartupService: logger.warn (connection replaced)
      BaileysStartupService->>BaileysStartupService: do not call connectToWhatsapp
    else [statusCode in codesToNotReconnect]
      BaileysStartupService->>BaileysStartupService: do not call connectToWhatsapp
    else [statusCode not in codesToNotReconnect]
      BaileysStartupService->>BaileysStartupService: connectToWhatsapp(phoneNumber)
    end
  else [connection != close]
    BaileysStartupService->>BaileysStartupService: other connection.update handling
  end
Loading

Sequence diagram for createClient isConnecting guard against concurrent reconnects

sequenceDiagram
  actor Integration
  participant Baileys
  participant BaileysStartupService
  participant WASocket

  Integration->>BaileysStartupService: connectToWhatsapp(number)
  BaileysStartupService->>BaileysStartupService: createClient(number)
  BaileysStartupService->>BaileysStartupService: set isConnecting = true
  BaileysStartupService->>WASocket: makeWASocket(socketConfig)
  BaileysStartupService->>BaileysStartupService: eventHandler()
  BaileysStartupService->>BaileysStartupService: set isConnecting = false

  Baileys-->>BaileysStartupService: connection.update (close)
  BaileysStartupService->>BaileysStartupService: shouldReconnect?
  alt [shouldReconnect]
    BaileysStartupService->>BaileysStartupService: connectToWhatsapp(number)
    BaileysStartupService->>BaileysStartupService: createClient(number)
    alt [isConnecting == true]
      BaileysStartupService->>BaileysStartupService: logger.warn (connection attempt already in progress)
      BaileysStartupService-->>Integration: return existing client
    else [isConnecting == false]
      BaileysStartupService->>BaileysStartupService: set isConnecting = true
      BaileysStartupService->>WASocket: makeWASocket(socketConfig)
      BaileysStartupService->>BaileysStartupService: eventHandler()
      BaileysStartupService->>BaileysStartupService: set isConnecting = false
    end
  else [!shouldReconnect]
    BaileysStartupService->>BaileysStartupService: do not reconnect
  end
Loading

File-Level Changes

Change Details Files
Handle DisconnectReason.connectionReplaced as a terminal state and avoid automatic reconnect loops, with clearer logging for session takeover scenarios.
  • Extend the codesToNotReconnect array to include DisconnectReason.connectionReplaced alongside other terminal disconnect codes.
  • Compute shouldReconnect from the updated terminal codes list to skip automatic reconnect when the status code indicates a replaced connection.
  • Add a structured warning log when statusCode equals DisconnectReason.connectionReplaced, explaining that the session was taken over elsewhere and requires a new QR or explicit reconnect.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
Introduce a simple instance-level connection lock to prevent concurrent createClient/connectToWhatsapp executions from racing on the same Baileys session.
  • Add a private isConnecting boolean flag to BaileysStartupService, initialized as false.
  • Short-circuit createClient when isConnecting is true, logging a warning and returning the current client instead of starting another connection.
  • Wrap the main createClient flow in a try/finally block that sets isConnecting to true at the start and reliably resets it to false at the end.
  • Refactor createClient body into the guarded section without changing existing connection, proxy, socketConfig, and event wiring logic aside from indentation and the new guard.
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • The isConnecting guard in createClient returns this.client when a connection is already in progress, but this may still be undefined for the first concurrent caller; consider tracking and returning a shared connection Promise instead so callers always receive a valid socket or a clear error.
  • The log message for DisconnectReason.connectionReplaced repeats the literal code 440; to avoid divergence if the enum changes, consider deriving the numeric code from the enum or centralizing the mapping so the message stays consistent with the actual value.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `isConnecting` guard in `createClient` returns `this.client` when a connection is already in progress, but this may still be `undefined` for the first concurrent caller; consider tracking and returning a shared connection Promise instead so callers always receive a valid socket or a clear error.
- The log message for `DisconnectReason.connectionReplaced` repeats the literal code `440`; to avoid divergence if the enum changes, consider deriving the numeric code from the enum or centralizing the mapping so the message stays consistent with the actual value.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@PhyBruno
PhyBruno changed the base branch from main to develop July 24, 2026 20:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants