fix(baileys): clear stale credentials when a 401 closes the initial connection - #2680
Open
michumichifu wants to merge 1 commit into
Open
Conversation
…onnection An instance that loses its session becomes permanently unpairable. When the connection closes with loggedOut before any QR code has been issued, connectionUpdate() takes the isInitialConnection early return and leaves the stored credentials untouched. Those credentials are in a half-valid state: the session is gone, so registered is false, but the identity is still there (me and account survive). Baileys reads that on the next attempt and does the reasonable thing with the information it has: it tries to re-authenticate as that identity rather than requesting a pairing code. WhatsApp answers 401 because the session no longer exists, the connection closes, and we land back on the same early return. The instance loops roughly every ten seconds with hasQr: false and never emits a code, so GET /instance/connect keeps returning an empty qrCode object, the Manager dialog spins forever, and a phone scanning any older code is told to check its internet connection. Recovering from this currently requires stopping the container and deleting the Session row by hand, because an instance still running rewrites the credentials from memory as soon as they are removed. Recreating the instance also works, but it changes the instance id and token and breaks every n8n flow, Chatwoot inbox and third-party integration pointing at it. Clear the credentials on that path so the next attempt starts clean and can pair. The cleanup already existed inside logoutInstance(); it is extracted into clearStoredCredentials() and reused, so both paths stay in sync.
Contributor
Reviewer's GuideRefactors Baileys WhatsApp service credential cleanup into a reusable method and invokes it on 401/loggedOut during the initial connection path to clear half-valid credentials that otherwise make an instance permanently unpairable. Sequence diagram for handling loggedOut during initial WhatsApp connectionsequenceDiagram
participant BaileysClient
participant BaileysStartupService
participant Database
participant Cache
BaileysClient->>BaileysStartupService: connectionUpdate(state=close, statusCode=DisconnectReason.loggedOut)
BaileysStartupService->>BaileysStartupService: isInitialConnection
alt [isInitialConnection and statusCode === DisconnectReason.loggedOut]
BaileysStartupService->>BaileysStartupService: clearStoredCredentials()
BaileysStartupService->>Database: delete Session credentials
BaileysStartupService->>Cache: clear auth cache
BaileysStartupService->>BaileysStartupService: stateConnection = { state: close, statusReason: 401 }
end
BaileysStartupService->>BaileysStartupService: logger.info("Initial connection closed, waiting for QR code generation...")
BaileysStartupService-->>BaileysClient: return
Note over BaileysClient,BaileysStartupService: Next connect attempt starts without stored credentials and can request a pairing QR code
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
An instance that loses its WhatsApp session becomes permanently unpairable. No amount of scanning fixes it, and the failure is reported to the user as a phone/network problem, which is why it took us a long time to trace.
When the connection closes with
loggedOutbefore any QR code has been issued,connectionUpdate()takes this early return and the stored credentials are never touched:Those credentials are in a half-valid state: the session is gone so
registeredisfalse, but the identity survives. Here is the actual comparison from our server, between a stuck instance and a healthy one created seconds earlier:registeredfalsefalseme.id15108804692:5@s.whatsapp.netaccountcredsGET /instance/connect{"count":0}Baileys then does the reasonable thing with the information it has: it tries to re-authenticate as that identity instead of requesting a pairing code. WhatsApp answers
401because the session no longer exists, the connection closes, and we are back at the same early return.The result is a loop roughly every ten seconds that never emits a code:
Downstream,
GET /instance/connectkeeps returning an emptyqrCodeobject, so the Manager's QR dialog spins forever, and anyone scanning an older code gets "Could not log in. Check your phone's internet connection and scan the QR code again" — pointing the user at their phone rather than at the server.Why the existing cleanup does not catch this
logoutInstance()already wipes credentials properly, andmonitor.service.tscallscleaningUp()on thelogout.instanceevent. Neither runs here, because the early return happens before the code that emits that event.Fix
Clear the credentials on that path, so the next attempt starts clean and can pair.
The cleanup logic already existed inside
logoutInstance(); this extracts it intoclearStoredCredentials()and reuses it, so both paths stay in sync rather than growing a second copy. Behaviour is unchanged for every status code other thanloggedOut.Why this matters beyond one instance
Recovering from this state currently requires stopping the container and deleting the
Sessionrow by hand — an instance that is still running rewrites the credentials from memory the moment they are deleted, which makes the obvious fix look like it does not work.The other workaround is deleting and recreating the instance, which is what most people end up doing. That changes the instance id and token, and breaks every n8n flow, Chatwoot inbox and third-party integration pointing at it. For anyone running instances for clients, that is an expensive way out of a state the server can clear by itself.
Testing
tsc --noEmit: clean across the project (with the Prisma client generated).eslint: clean on the changed file.npm run build: succeeds.Diagnosed on a production deployment running
2.4.0-rc2. Manually clearing the credentials exactly as this patch does made the affected instance pair on the first scan, after an afternoon of failed attempts. A second instance on the same server still shows the same signature (connectionStatus: closewith credentials present), which is what the table above compares.Summary by Sourcery
Ensure WhatsApp Baileys instances clear rejected credentials on initial 401 closures so they can pair again instead of looping without QR codes.
Bug Fixes: