Skip to content

fix(instance): prevent GetQr from disconnecting active logged-in session - #149

Open
iagocotta wants to merge 1 commit into
evolution-foundation:developfrom
iagocotta:fix/qr-disconnect-active-session
Open

fix(instance): prevent GetQr from disconnecting active logged-in session#149
iagocotta wants to merge 1 commit into
evolution-foundation:developfrom
iagocotta:fix/qr-disconnect-active-session

Conversation

@iagocotta

@iagocotta iagocotta commented Jul 31, 2026

Copy link
Copy Markdown

Problema

Quando GET /instance/qr é chamado em uma instância que já está logada (client.IsLoggedIn() == true), a implementação atual do GetQr chama whatsmeowService.StartInstance() mesmo assim, derrubando o cliente whatsmeow ativo e forçando um novo pareamento.

Reprodução

  1. Parear uma nova instância via QR Code — receber PairSuccess
  2. Logo após o PairSuccess, o whatsmeow entra em Connected: true, LoggedIn: false por ~5–15 s enquanto faz o history sync inicial
  3. Qualquer frontend fazendo polling em GET /instance/qr durante esse intervalo (muito comum — a maioria dos managers faz polling a cada 3–5 s para detectar a conexão) cai no branch com bug
  4. Como IsLoggedIn() retorna true em qualquer momento após o login completar, o handler chama StartInstance() → whatsmeow desconecta → novo QR code é emitido
  5. Usuário vê a sessão "morrer" segundos após parear com sucesso

Sequência de webhooks observada

13:32:48  PairSuccess           # pareamento OK
13:33:01-04  HistorySync (x5)   # whatsmeow sincronizando (LoggedIn:false)
13:33:09  Disconnected          # GET /instance/qr disparou StartInstance
13:33:13  Connected             # whatsmeow reconectando
13:33:14  QRCode (4/5)          # sessão perdida, novo QR

Isso não ocorre ao usar o próprio manager web do Evolution-Go, porque ele usa SSE para mudanças de status em vez de fazer polling em /instance/qr.

Causa raiz

pkg/instance/service/instance_service.go no método GetQr:

// Se não há cliente ou o cliente está logado, precisamos iniciar um novo cliente
if client == nil || client.IsLoggedIn() {   // <-- também entra quando LOGADO
    ...
    err := i.whatsmeowService.StartInstance(instance.Id)   // <-- derruba o cliente ativo
    time.Sleep(3 * time.Second)
    ...
}

O comentário ("iniciar um novo cliente") revela que a intenção original era (re)iniciar quando não houvesse cliente. Mas a condição também dispara para clientes logados, provavelmente para forçar um novo QR quando o chamador quer explicitamente re-parear. Esse caminho deveria ser um opt-in explícito, não o comportamento padrão do GetQr.

Correção

Separar os dois casos:

  • Se client.IsLoggedIn() → retorna "session already logged in" imediatamente, sem tocar no StartInstance. Chamadores que queiram forçar re-pareamento podem fazer logout antes via DELETE /instance/logout ou usar POST /instance/forcereconnect.
  • Se client == nil ou !client.IsConnected() && !client.IsLoggedIn() → inicia a instância como antes.
  • Caso contrário (cliente existe, não conectado, não logado) → aguarda um QR code existente.
if client != nil && client.IsLoggedIn() {
    logger.LogInfo("[%s] Client is already logged in — returning 'session already logged in' (StartInstance skipped to avoid disconnect)", instance.Id)
    return nil, fmt.Errorf("session already logged in")
}

if client == nil || (!client.IsConnected() && !client.IsLoggedIn()) {
    // caminho existente do StartInstance
}

Comportamento quando o chamador faz polling em /instance/qr numa instância logada:

  • Antes: whatsmeow desconectado, novo QR emitido, sessão perdida
  • Depois: HTTP 400 "session already logged in" (já era o comportamento documentado da segunda checagem de IsLoggedIn dentro do branch — essa correção apenas faz o short-circuit antes da chamada destrutiva ao StartInstance)

Impacto

  • Sem mudança de API — o payload de resposta para instâncias logadas já é "session already logged in"; essa correção apenas torna a resposta consistente independentemente do timing e remove o efeito colateral
  • Sem mudança de configuração necessária
  • Frontends que faziam polling em /instance/qr para detectar conexão agora podem fazer isso com segurança, sem risco de desconectar a instância que acabaram de parear

Testado

  • Pareada uma nova instância com o frontend fazendo polling em /instance/qr a cada 4 s
  • Antes da correção: sessão caiu ~15 s após o PairSuccess
  • Depois da correção: sessão manteve-se ativa, HistorySync foi concluído, instância funcional

Summary by Sourcery

Prevent QR retrieval from restarting already logged-in WhatsApp instances, avoiding unintended disconnects during polling.

Bug Fixes:

  • Return a 'session already logged in' error from GetQr without restarting the instance when a client is already logged in, preventing forced re-pairing triggered by frontend polling.
  • Restrict instance restarts for QR generation to cases where there is no client or the client is neither connected nor logged in, avoiding unnecessary disconnects of active sessions.

@sourcery-ai

sourcery-ai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Reviewer's guide (collapsed on small PRs)

Reviewer's Guide

Adjusts GetQr behavior to avoid restarting an already logged-in WhatsApp instance, preventing active sessions from being disconnected when the QR endpoint is polled, and refines the conditions and logging around when StartInstance is invoked.

File-Level Changes

Change Details Files
Guard GetQr against restarting logged-in clients and refine the StartInstance conditions to only run when there is no client or it is disconnected and not logged in.
  • Add an early return in GetQr when a client exists and is logged in, logging the condition and returning a 'session already logged in' error without calling StartInstance.
  • Change the StartInstance branch to trigger only when there is no client or the client is neither connected nor logged in, instead of triggering when the client is logged in.
  • Improve logging messages to distinguish between creating a new client, restarting an existing disconnected/unlogged client, and skipping restart for already logged-in sessions.
pkg/instance/service/instance_service.go

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

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:

  • Consider defining a sentinel error (e.g., ErrSessionAlreadyLoggedIn) instead of fmt.Errorf("session already logged in") so callers can reliably distinguish this case from other errors when mapping to HTTP status codes.
  • The new logging messages are quite verbose and repeated on every GetQr call; consider reducing or standardizing them to avoid log noise while still capturing the key state transitions.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider defining a sentinel error (e.g., `ErrSessionAlreadyLoggedIn`) instead of `fmt.Errorf("session already logged in")` so callers can reliably distinguish this case from other errors when mapping to HTTP status codes.
- The new logging messages are quite verbose and repeated on every `GetQr` call; consider reducing or standardizing them to avoid log noise while still capturing the key state transitions.

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.

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.

1 participant