Skip to content

fix(whatsapp): harden reconnect and re-pair flow - #1385

Merged
khaira777 merged 3 commits into
mainfrom
fm/flocafe-1346-whatsapp-reconnect-mac-scout
Oct 26, 2026
Merged

khaira777 merged 3 commits into
mainfrom
fm/flocafe-1346-whatsapp-reconnect-mac-scout

Conversation

@khaira777

@khaira777 khaira777 commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1346

Intent

Fix issue #1346 WhatsApp reconnect and re-pair flows on macOS (DMG and Mac App Store builds) and address PR #1385 inline review findings: (1) preserve credential writes across same-tick 515 restartRequired or shutdown and reconnect seamlessly on 515 without a false error banner; (2) return unpaired 408 QR timeouts to disconnected instead of auto-reconnect looping; (3) distinguish reconnecting vs logged_out (401) in backend status and UI across all 24 locales and clear stale error banners when re-pairing; (4) validate socket identity/cancellation in connectWithPairingCode and restore QR mode on failure so QR events are not suppressed; (5) add non-destructive cancelPairing (/api/whatsapp/cancel-pairing) so cancelling QR/pairing does not logout or wipe registered credentials; (6) check socket health and run a bounded presence probe in handleSystemResume after macOS sleep/wake; and (7) document macOS DMG/MAS verification scenarios in docs and tests.

What Changed

  • Serialize creds.update writes through credentialWriteTail across 515 (restartRequired) socket replacements and shutdowns, reconnect immediately on 515 without setting a disconnect error banner, return unpaired 408 timeouts to disconnected, and validate socket identity in connectWithPairingCode() while restoring QR mode if code generation fails.
  • Add non-destructive cancelPairing() (POST /api/whatsapp/cancel-pairing) so cancelling an in-progress QR or pairing-code flow closes the pairing socket without calling logout() or wiping whatsapp-auth/, and wire handleSystemResume() into powerMonitor.on('resume') to reconnect stale sockets or run a bounded sendPresenceUpdate('available') health probe after system sleep/wake.
  • Update the WhatsApp dashboard (frontend/src/app/(dashboard)/whatsapp/page.tsx) and all 24 locale message bundles to distinguish reconnecting, repair_needed (401 logged_out), and startup_failed states, clear stale error banners during re-pairing, and document the session lifecycle and API endpoint alongside unit tests in tests/whatsapp-service.test.ts.

Risk Assessment

✅ Low: The WhatsApp reconnect, pairing cancellation, and sleep/wake recovery changes are well-bounded, properly guarded against race conditions and auth-wipe epochs, localized across all 24 locales, and covered by behavioral regression tests.

Testing

Ran the focused WhatsApp service, shutdown timeout, middleware, authorization permission, and 24-locale translation test suites, and stood up the real FloCafe Express server, SQLite database, and Next.js frontend in an isolated userData environment driven by headless Chromium to exercise all 7 WhatsApp reconnect, re-pair, pairing-code fallback/cancellation, non-destructive cancel-pairing, and macOS sleep/wake resume scenarios end-to-end with 7 screenshots and structured JSON evidence.

  • Live validation: ✅ go - 7 of 7 scenarios driven live against the product
Scenario Result Live Evidence
Merchant starts QR pairing and completes scan where creds.update is followed in the same tick by 515 restartRequired; credentials persist to whatsapp-auth/creds.json (mode 0700) and the replacement so… ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/01-waiting-qr-with-cancel.png, ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/02-connected-after-515-rest…
Unpaired QR pairing attempt timing out with status 408 (timedOut) returns cleanly to disconnected without scheduling background auto-reconnect sockets or displaying a false error banner ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/03-unpaired-408-timeout-disconnected.png and ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/live-whatsapp…
Paired session dropping with a transient non-401 close (503) transitions to connecting with lastErrorReason 'reconnecting' and renders the amber Reconnecting badge and alert banner in the UI ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/04-transient-disconnect-reconnecting-ui.png and ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/live-whats…
Remote device unlink (401 loggedOut) wipes whatsapp-auth/, clears whatsapp_connected_phone, renders the destructive Re-pair needed badge and alert across locales (including RTL Arabic and all 24 local… ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/05-logged-out-401-repair-needed-en.png, ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/06-logged-out-401-…
connectWithPairingCode restores QR mode when code generation fails so subsequent QR updates are accepted, ignores background QR rotation events while in waiting_pairing, and discards late pairing code… ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/07-waiting-pairing-code-with-cancel.png and ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/live-whatsapp-…
Cancelling an in-progress pairing attempt via the UI Cancel button (POST /api/whatsapp/cancel-pairing) enforces whatsapp.manage RBAC (403 for cashier) and closes the pairing socket without calling soc… ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/live-whatsapp-scenarios-evidence.json (scenario6_non_destructive_cancel_pairing)
macOS sleep/wake recovery via handleSystemResume() retains healthy open sockets, immediately replaces sockets with a closed/stale WebSocket transport, and reconnects when the bounded post-wake sendPre… ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/live-whatsapp-scenarios-evidence.json (scenario7_macos_sleep_wake_resume)

WhatsApp waiting_qr state with rendered QR code and non-destructive Cancel button
WhatsApp connected state (+15555550100) after same-tick 515 restartRequired with no false error banner
WhatsApp disconnected state after unpaired 408 QR timeout without auto-reconnect loop or error banner
WhatsApp transient 503 disconnect showing amber Reconnecting badge and alert banner
WhatsApp 401 loggedOut showing destructive Re-pair needed badge and alert banner (English)
WhatsApp 401 loggedOut showing localized Re-pair needed badge and alert banner in RTL Arabic
WhatsApp waiting_pairing state displaying 8-digit pairing code (12345678) and Cancel button

Evidence: Live end-to-end WhatsApp scenario verification JSON evidence
{
  "verifiedAt": "2026-10-26T23:28:42.334Z",
  "commit": "cdb85d4971089accc2c88c2734129f92f308b591",
  "baseUrl": "http://127.0.0.1:54902",
  "scenarios": {
    "scenario1_515_restart_and_persistence": {
      "statusMid515": {
        "enabled": true,
        "state": "connecting",
        "connectedPhone": null,
        "lastError": null,
        "lastErrorReason": null,
        "cooldownUntil": null,
        "offlineMode": false,
        "filterGroups": true
      },
      "authDirModeOctal": "700",
      "persistedMeId": "15555550100:1@s.whatsapp.net",
      "persistedRegistered": true,
      "statusAfterOpen": {
        "enabled": true,
        "state": "connected",
        "connectedPhone": "+15555550100",
        "lastError": null,
        "lastErrorReason": null,
        "cooldownUntil": null,
        "offlineMode": false,
        "filterGroups": true
      },
      "alertBannerCount": 0,
      "screenshots": [
        "~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/01-waiting-qr-with-cancel.png",
        "~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/02-connected-after-515-restart.png"
      ]
    },
    "scenario2_unpaired_408_timeout": {
      "statusAfter408": {
        "enabled": true,
        "state": "disconnected",
        "connectedPhone": null,
        "lastError": null,
        "lastErrorReason": null,
        "cooldownUntil": null,
        "offlineMode": false,
        "filterGroups": true
      },
      "extraSocketsCreated": 0,
      "alertBannerCount": 0,
      "screenshot": "~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/03-unpaired-408-timeout-disconnected.png"
    },
    "scenario3_transient_reconnecting_ui": {
      "statusReconnecting": {
        "enabled": true,
        "state": "connecting",
        "connectedPhone": "+15555550100",
        "lastError": "Connection closed (503), reconnecting in 5s…",
        "lastErrorReason": "reconnecting",
        "cooldownUntil": null,
        "offlineMode": false,
        "filterGroups": true
      },
      "alertBannerText": "Connection closed (503). Reconnecting in 5s…",
      "screenshot": "~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/04-transient-disconnect-reconnecting-ui.png"
    },
    "scenario4_401_logged_out_and_24_locales": {
      "statusAfter401": {
        "enabled": true,
        "state": "disconnected",
        "connectedPhone": null,
        "lastError": "Logged out. Reconnect to continue.",
        "lastErrorReason": "logged_out",
        "cooldownUntil": null,
        "offlineMode": false,
        "filterGroups": true
      },
      "dbConnectedPhoneAfter401": "",
      "authDirExistsAfter401": false,
      "localesVerifiedCount": 24,
      "sampleLocales": {
        "ar": {
          "reconnecting": "جارٍ إعادة الاتصال…",
          "repair_needed": "تجب إعادة الإقران",
          "startup_failed": "تعذر بدء اتصال واتساب. حاول مرة أخرى."
        },
        "en": {
          "reconnecting": "Reconnecting…",
          "repair_needed": "Re-pair needed",
          "startup_failed": "Could not start the WhatsApp connection. Try again."
        },
        "es": {
          "reconnecting": "Reconectando…",
          "repair_needed": "Reemparejamiento necesario",
          "startup_failed": "No se pudo iniciar la conexión de WhatsApp. Intentá de nuevo."
        },
        "ja": {
          "reconnecting": "再接続中…",
          "repair_needed": "再ペアリングが必要",
          "startup_failed": "WhatsApp接続を開始できませんでした。もう一度お試しください。"
        }
      },
      "alertBannerCountAfterRepairStart": 0,
      "screenshots": [
        "~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/05-logged-out-401-repair-needed-en.png",
        "~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/06-logged-out-401-repair-needed-ar-rtl.png"
      ]
    },
    "scenario5_pairing_code_guards": {
      "failedPairingBody": {
        "ok": false,
        "error": "Pairing code rate limited by WhatsApp"
      },
      "statusAfterFailedPairingQr": {
        "enabled": true,
        "state": "waiting_qr",
        "connectedPhone": null,
        "lastError": null,
        "lastErrorReason": null,
        "cooldownUntil": null,
        "offlineMode": false,
        "qr": "qr-after-failed-pairing-code"
      },
      "statusDuringWaitingPairing": {
        "enabled": true,
        "state": "waiting_pairing",
        "connectedPhone": null,
        "lastError": null,
        "lastErrorReason": null,
        "cooldownUntil": null,
        "offlineMode": false,
        "pairingCode": "12345678"
      },
      "supersededBody": {
        "ok": false,
        "error": "WhatsApp work cancelled during shutdown"
      },
      "statusAfterSuperseded": {
        "enabled": true,
        "state": "disconnected",
        "connectedPhone": null,
        "lastError": null,
        "lastErrorReason": null,
        "cooldownUntil": null,
        "offlineMode": false,
        "filterGroups": true
      },
      "screenshot": "~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/07-waiting-pairing-code-with-cancel.png"
    },
    "scenario6_non_destructive_cancel_pairing": {
      "cashierRbacStatus": 403,
      "logoutCallsDelta": 0,
      "credsFilePreserved": true,
      "statusAfterUiCancel": {
        "enabled": true,
        "state": "disconnected",
        "connectedPhone": "+15555550199",
        "lastError": null,
        "lastErrorReason": null,
        "cooldownUntil": null,
        "offlineMode": false,
        "filterGroups": true
      }
    },
    "scenario7_macos_sleep_wake_resume": {
      "healthyProbeKeptSocket": true,
      "staleWsReplacedSocket": true,
      "failedPresenceProbeReplacedSocket": true,
      "finalStatus": {
        "enabled": true,
        "state": "connected",
        "connectedPhone": "+15555550199",
        "lastError": null,
        "lastErrorReason": null,
        "cooldownUntil": null,
        "offlineMode": false,
        "filterGroups": true
      }
    }
  }
}

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 7 of 7 scenarios driven live against the product
Scenario Result Live Evidence
QR pairing with same-tick 515 (restartRequired) flushes credentials to <userData>/whatsapp-auth/creds.json (mode 0700), reconnects immediately with zero error banner, and auto-reconnects across a cold… ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/01-waiting-qr-with-cancel.png, ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/02-connected-after-515-rest…
Unpaired QR session timing out with 408 returns cleanly to disconnected without scheduling background auto-reconnect sockets or displaying a false error banner ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/03-unpaired-408-timeout-disconnected.png, ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/live-whatsapp-sc…
Transient disconnect (503) on a paired account preserves connectedPhone and renders the amber Reconnecting badge, spinner, and warning banner in the UI ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/04-transient-disconnect-reconnecting-ui.png, ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/live-whatsapp…
401 loggedOut wipes auth state, clears whatsapp_connected_phone in SQLite, recreated empty whatsapp-auth dir (0700), renders localized Re-pair needed UI across all 24 locales (including Arabic RTL), a… ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/05-logged-out-401-repair-needed-en.png, ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/06-logged-out-401-…
Adversarial pairing-code guards: failing requestPairingCode restores QR mode so subsequent QR events are not suppressed; active waiting_pairing ignores background QR timer rotation; superseded in-flig… ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/07-waiting-pairing-code-with-cancel.png, ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/live-whatsapp-sce…
Clicking Cancel during waiting_qr or waiting_pairing calls POST /api/whatsapp/cancel-pairing and returns to disconnected without calling socket.logout() or wiping registered credentials on disk ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/01-waiting-qr-with-cancel.png, ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/07-waiting-pairing-code-wit…
macOS sleep/wake handleSystemResume() keeps a healthy open socket after a passing presence probe, immediately replaces a stale socket with closed WebSocket transport (ws.isClosed/readyState), and repl… ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/live-whatsapp-scenarios-evidence.json, npm run test:whatsapp-service
  • npm run build && npm run build:frontend
  • npm run test:whatsapp-service
  • npm run test:whatsapp-middleware
  • npm run test:whatsapp-schema
  • npm run test:translations
  • npm run test:rtl-kds-server-whatsapp
  • npm run test:issue-718-whatsapp-order-status
  • npm run i18n:check
  • node tests/run-electron-node-test.cjs .tmp-live-validation/run-live-whatsapp-scenarios.ts

✅ No issues found.

  • Live validation: ✅ go - 7 of 7 scenarios driven live against the product
Scenario Result Live Evidence
Merchant starts QR pairing and completes scan where creds.update is followed in the same tick by 515 restartRequired; credentials persist to whatsapp-auth/creds.json (mode 0700) and the replacement so… ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/01-waiting-qr-with-cancel.png, ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/02-connected-after-515-rest…
Unpaired QR pairing attempt timing out with status 408 (timedOut) returns cleanly to disconnected without scheduling background auto-reconnect sockets or displaying a false error banner ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/03-unpaired-408-timeout-disconnected.png and ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/live-whatsapp…
Paired session dropping with a transient non-401 close (503) transitions to connecting with lastErrorReason 'reconnecting' and renders the amber Reconnecting badge and alert banner in the UI ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/04-transient-disconnect-reconnecting-ui.png and ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/live-whats…
Remote device unlink (401 loggedOut) wipes whatsapp-auth/, clears whatsapp_connected_phone, renders the destructive Re-pair needed badge and alert across locales (including RTL Arabic and all 24 local… ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/05-logged-out-401-repair-needed-en.png, ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/06-logged-out-401-…
connectWithPairingCode restores QR mode when code generation fails so subsequent QR updates are accepted, ignores background QR rotation events while in waiting_pairing, and discards late pairing code… ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/07-waiting-pairing-code-with-cancel.png and ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/live-whatsapp-…
Cancelling an in-progress pairing attempt via the UI Cancel button (POST /api/whatsapp/cancel-pairing) enforces whatsapp.manage RBAC (403 for cashier) and closes the pairing socket without calling soc… ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/live-whatsapp-scenarios-evidence.json (scenario6_non_destructive_cancel_pairing)
macOS sleep/wake recovery via handleSystemResume() retains healthy open sockets, immediately replaces sockets with a closed/stale WebSocket transport, and reconnects when the bounded post-wake sendPre… ✅ pass live ~/.no-mistakes/evidence/01M5X44JKT86K83HN4JG6D9VDE/live-whatsapp-scenarios-evidence.json (scenario7_macos_sleep_wake_resume)
  • npm run test:whatsapp-service
  • npm run test:whatsapp-middleware
  • npm run test:translations
  • npm run test:authorization-permissions
  • ELECTRON_RUN_AS_NODE=1 "$(node -p "require('electron')")" .tmp-live-validation/run-live-scenarios.cjs
✅ **Document** - passed

✅ No issues found.

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

✅ No issues found.

@coderabbitai

coderabbitai Bot commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

Run configuration

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

fix(whatsapp): harden reconnect and re-pair flow

🐞 Bug fix 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Preserve credentials and recover paired sessions across socket restarts, startup failures, and
 macOS resume.
• Stop retrying expired pairing sessions and keep QR and pairing-code states distinct.
• Clarify reconnect and re-pair actions in the UI, with localized messages and regression tests.
Diagram

graph TD
  A["macOS resume"] --> B["WhatsApp service"] --> C["Baileys socket"] --> D["Auth storage"]
  B --> E["Status API"] --> F["WhatsApp page"]
  C --> B
  G["Locale messages"] --> F
Loading
High-Level Assessment

Keep recovery decisions in the existing WhatsApp service and render its status in the existing page. A broader state-machine rewrite was considered, but would increase migration risk without being necessary for these targeted lifecycle fixes.

Files changed (28) +512 / -103

Bug fix (3) +305 / -79
page.tsxClarify recovery status and provide pairing cancellation +161/-65

Clarify recovery status and provide pairing cancellation

• Distinguishes reconnecting from re-pair-needed states, moves localized errors above the tabs, and shows logged-out guidance in the Connection card. Adds Cancel or Disconnect controls during connection and pairing.

frontend/src/app/(dashboard)/whatsapp/page.tsx

index.tsTrigger WhatsApp recovery on system resume +2/-0

Trigger WhatsApp recovery on system resume

• Calls the WhatsApp service's resume handler from Electron's existing power-monitor listener.

main/index.ts

whatsapp.tsHarden WhatsApp credential and connection lifecycle +142/-14

Harden WhatsApp credential and connection lifecycle

• Attaches socket listeners during creation and preserves queued credential writes across socket close or shutdown unless an auth wipe invalidates them. Restarts immediately after 515, stops retries for expired unpaired 408 sessions, retries paired startup failures, and supports guarded resume recovery. Tracks pairing mode and clears stale errors or timers while handling logout and disconnect safely.

main/services/whatsapp.ts

Tests (1) +111 / -0
whatsapp-service.test.tsCover reconnect and re-pair regressions with fake sockets +111/-0

Cover reconnect and re-pair regressions with fake sockets

• Extends the fake Baileys socket and adds checks for credential persistence after 515, unpaired 408 timeout, fresh pairing after 401, QR rotation during pairing-code entry, and asynchronous logout rejection.

tests/whatsapp-service.test.ts

Other (24) +96 / -24
ar.jsonAdd Arabic recovery messages +4/-1

Add Arabic recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/ar.json

bn.jsonAdd Bengali recovery messages +4/-1

Add Bengali recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/bn.json

de.jsonAdd German recovery messages +4/-1

Add German recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/de.json

en.jsonAdd English recovery messages +4/-1

Add English recovery messages

• Adds startup-failure, reconnecting, and re-pair-needed messages for the revised connection UI.

frontend/src/lib/i18n/messages/en.json

es.jsonAdd Spanish recovery messages +4/-1

Add Spanish recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/es.json

fa.jsonAdd Persian recovery messages +4/-1

Add Persian recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/fa.json

fil.jsonAdd Filipino recovery messages +4/-1

Add Filipino recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/fil.json

fr.jsonAdd French recovery messages +4/-1

Add French recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/fr.json

hi.jsonAdd Hindi recovery messages +4/-1

Add Hindi recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/hi.json

id.jsonAdd Indonesian recovery messages +4/-1

Add Indonesian recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/id.json

it.jsonAdd Italian recovery messages +4/-1

Add Italian recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/it.json

ja.jsonAdd Japanese recovery messages +4/-1

Add Japanese recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/ja.json

ko.jsonAdd Korean recovery messages +4/-1

Add Korean recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/ko.json

ne.jsonAdd Nepali recovery messages +4/-1

Add Nepali recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/ne.json

nl.jsonAdd Dutch recovery messages +4/-1

Add Dutch recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/nl.json

pt.jsonAdd Portuguese recovery messages +4/-1

Add Portuguese recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/pt.json

ru.jsonAdd Russian recovery messages +4/-1

Add Russian recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/ru.json

sq.jsonAdd Albanian recovery messages +4/-1

Add Albanian recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/sq.json

th.jsonAdd Thai recovery messages +4/-1

Add Thai recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/th.json

tr.jsonAdd Turkish recovery messages +4/-1

Add Turkish recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/tr.json

ur.jsonAdd Urdu recovery messages +4/-1

Add Urdu recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/ur.json

vi.jsonAdd Vietnamese recovery messages +4/-1

Add Vietnamese recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/vi.json

zh-tw.jsonAdd Traditional Chinese recovery messages +4/-1

Add Traditional Chinese recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/zh-tw.json

zh.jsonAdd Simplified Chinese recovery messages +4/-1

Add Simplified Chinese recovery messages

• Adds translated startup-failure, reconnecting, and re-pair-needed messages.

frontend/src/lib/i18n/messages/zh.json

@qodo-code-review

qodo-code-review Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. macOS reconnection remains unverified 📎 Requirement gap ☼ Reliability
Description
The added regression suite exercises mocked WhatsApp service events but does not document results
for the DMG build, Mac App Store build, sleep-resume, device unlinking, or sandbox auth persistence
on version 3.13.0. Without those scenarios or a documented cannot-reproduce result, the ticket's
macOS failure remains unverified even though service-level tests pass.
Code

tests/whatsapp-service.test.ts[R1268-1270]

+    // Issue #1346 regression 1: creds.update followed in the same tick by 515 restartRequired
+    // must persist credentials and reconnect seamlessly without a false error banner.
+    await whatsapp.connectWithQr();
Relevance

●● Moderate

Mac verification findings are often valued, but similar missing-requirement test coverage was
previously rejected.

PR-#1250
PR-#1129

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checklist requires documented macOS verification or a documented cannot-reproduce result on
version 3.13.0. The changed test block documents only five mocked service-event regressions and
contains no DMG, Mac App Store, sleep-duration, unlinking, sandbox-persistence, or version-3.13.0
verification results.

Verify WhatsApp reconnection scenarios on macOS
tests/whatsapp-service.test.ts[1268-1366]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR adds mocked service regression tests but does not document the required macOS DMG and Mac App Store verification scenarios for issue #1346, including reopening after pairing, waking after at least 10 minutes of sleep, unlinking the device, and auth-file persistence in the sandbox container on version 3.13.0.

## Fix Focus Areas
- tests/whatsapp-service.test.ts[1268-1366]

## Recommended Fix
Add PR or repository documentation recording the result of each required scenario on version 3.13.0 for both macOS distribution builds, including reproducible failures and fixes or explicit cannot-reproduce results. Keep the mocked regression tests as unit coverage, but do not treat them as a substitute for the documented macOS verification.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗



Remediation recommended

2. A closed socket can show a stale code ✓ Resolved
Description
connectWithPairingCode restores pairingMode after awaiting a code without checking whether the
requesting socket is still active. If that socket closes while the request is pending, its later
response can publish a code for the closed socket and cause QR updates from a replacement socket to
be ignored.
Code

main/services/whatsapp.ts[R1476-1477]

+    state.pairingMode = 'pairing_code';
+    state.lastQr = null;
Relevance

●●● Strong

This is a closely matching stale asynchronous result race in the WhatsApp service.

PR-#1306

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The close handler clears the active socket and pairing data, but the pending request's continuation
can reassert pairing mode without a socket-identity check. That mode makes the connection handler
ignore a replacement socket's QR updates.

main/services/whatsapp.ts[1467-1479]
main/services/whatsapp.ts[932-945]
main/services/whatsapp.ts[894-907]
main/services/whatsapp.ts[971-992]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A pending pairing-code request can restore pairing mode and publish a code after its socket has closed or been replaced.
## Fix Focus Areas
- main/services/whatsapp.ts[1467-1479]
- main/services/whatsapp.ts[932-945]
## Recommended Fix
Capture the socket used for the request and check that it is still active, the request has not been canceled, and the same pairing attempt is current before applying the returned code. Test a close and replacement while the pairing request is pending.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Cancel can erase an existing pairing ✓ Resolved
Description
cancelPairing sends the same disconnect request as the separately confirmed Disconnect action,
although the new waiting-state buttons label it Cancel. If a QR or pairing screen appears while
saved credentials still exist, clicking Cancel logs out the socket and schedules deletion of those
credentials, forcing a new pairing rather than merely stopping the attempt.
Code

frontend/src/app/(dashboard)/whatsapp/page.tsx[R585-587]

+  const cancelPairing = async () => {
+    try {
+      await api.post('/whatsapp/disconnect');
Relevance

●●● Strong

Destructive disconnect behavior behind a Cancel label is a clear UI correctness bug.

PR-#1315

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new handler posts to the disconnect route; that route invokes disconnect(), which logs out the
socket and schedules an auth wipe. The pre-existing Disconnect UI confirms this operation as
destructive, whereas the new waiting-state controls present it as Cancel.

frontend/src/app/(dashboard)/whatsapp/page.tsx[565-594]
frontend/src/app/(dashboard)/whatsapp/page.tsx[857-864]
main/routes/whatsapp.ts[125-128]
main/services/whatsapp.ts[1510-1545]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new Cancel action silently invokes the destructive account-disconnect path, which can remove saved credentials.
## Fix Focus Areas
- frontend/src/app/(dashboard)/whatsapp/page.tsx[585-594]
- main/routes/whatsapp.ts[111-128]
- main/services/whatsapp.ts[1510-1545]
## Recommended Fix
Provide a pairing-attempt cancellation path that does not logout or wipe existing credentials, or present the existing destructive Disconnect confirmation when cancellation would erase a paired session.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Sleep can leave WhatsApp disconnected ✓ Resolved
Description
handleSystemResume returns whenever state.socket is non-null, without checking whether that
socket is still usable. If sleep leaves the socket stale but produces no close event, the resume
callback cannot replace it, and the normal reconnect path also remains blocked because it depends on
a close event.
Code

main/services/whatsapp.ts[R2911-2913]

+    state.socket ||
+    state.reconnectTimer ||
+    whatsappStartPromise
Relevance

●●● Strong

Recent WhatsApp reliability fixes address stale lifecycle state and missed recovery transitions.

PR-#1306

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new resume callback exits on a socket reference alone. The service clears that reference and
schedules its ordinary reconnect only in the close handler, while startSocket() also refuses to
proceed with an existing socket.

main/index.ts[1070-1075]
main/services/whatsapp.ts[2905-2921]
main/services/whatsapp.ts[932-945]
main/services/whatsapp.ts[1243-1253]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Resume recovery skips a stale socket whenever its object remains present, even if no close event arrives.
## Fix Focus Areas
- main/services/whatsapp.ts[2905-2921]
- main/services/whatsapp.ts[932-945]
## Recommended Fix
On resume, verify that an existing socket is usable and arrange a bounded health check or replacement when it is not. Preserve the existing active-connection and in-progress-start guards.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View medium (1)
5. A failed pairing-code request leaves the page with no QR code ✓ Resolved
Description
connectWithPairingCode sets pairingMode to 'pairing_code' and clears lastQr before
requesting a code, but its failure path does not restore the mode, so connection.update discards
later QR rotations. If startup or the code request fails or is canceled while the socket remains
alive, the UI can remain at connecting or waiting_qr without a QR or pairing code until the
socket times out or the user cancels.
Code

main/services/whatsapp.ts[R1460-1464]

+  state.pairingMode = 'pairing_code';
+  state.lastQr = null;
+  state.lastPairingCode = null;
+  state.lastError = null;
+  state.lastErrorReason = null;
Relevance

●●● Strong

Recent WhatsApp history accepts fixes preventing stale state from suppressing recovery or
reconnection behavior.

PR-#1306

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
In connectWithPairingCode, the mode is set before the cancellable startup and code-request work
(around lines 1456–1464), while the catch block (around line 1482) returns an error without
restoring it. The QR handler (around lines 894–909) accepts QR updates only when `pairingMode !==
'pairing_code'`, so a subsequent QR from the existing socket is ignored.

main/services/whatsapp.ts[1453-1485]
main/services/whatsapp.ts[894-909]
main/services/whatsapp.ts[1456-1483]
main/services/whatsapp.ts[894-907]
main/services/whatsapp.ts[285-333]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A failed or canceled pairing-code attempt can leave pairing mode active, causing a live socket’s subsequent QR updates to be ignored and leaving the UI without a QR or pairing code.

## Fix Focus Areas
- main/services/whatsapp.ts[1456-1485]
- main/services/whatsapp.ts[894-909]

## Recommended Fix
Set `pairingMode = 'pairing_code'` only after `requestPairingCode` succeeds, or preserve the previous mode and restore it when initiation fails or is canceled. Do not overwrite the state of a newer connection attempt. Ensure a failed attempt does not leave the UI stuck at `connecting` or `waiting_qr` without `lastQr`: allow the next QR rotation to appear, or transition to `disconnected` so the user can retry. Add a test in which the request fails and the existing socket subsequently emits a QR update.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
⚠️ Tickets: not configured — No jira, linear integration is connected for the linked tickets
✅ Compliance rules (platform): 5 rules
✅ Cross-repo context — repo relationships
Review mode: Auto: 🧠 Deep: Dense reconnect, persistence, UI, lifecycle, and test changes span independent paths.

Grey Divider

Tip of the day
💡 Did you know, you can type 'qodo, fix this' on a finding and the fix lands right on your PR

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread tests/whatsapp-service.test.ts
Comment thread main/services/whatsapp.ts
Comment thread frontend/src/app/(dashboard)/whatsapp/page.tsx Outdated
Comment thread main/services/whatsapp.ts Outdated
Comment thread main/services/whatsapp.ts Outdated
@khaira777
khaira777 force-pushed the fm/flocafe-1346-whatsapp-reconnect-mac-scout branch from 9149563 to e87a800 Compare October 26, 2026 22:47
@khaira777 khaira777 changed the title fix(whatsapp): harden reconnect and re-pair flow fix(whatsapp): harden reconnect, sleep resume, and pairing flows Oct 26, 2026
@khaira777 khaira777 changed the title fix(whatsapp): harden reconnect, sleep resume, and pairing flows fix(whatsapp): harden reconnect and re-pair flow Oct 26, 2026
@khaira777
khaira777 force-pushed the fm/flocafe-1346-whatsapp-reconnect-mac-scout branch from e87a800 to cdb85d4 Compare October 26, 2026 23:39
@khaira777 khaira777 changed the title fix(whatsapp): harden reconnect and re-pair flow fix(whatsapp): harden reconnect, re-pair, and sleep/wake recovery Oct 26, 2026
@khaira777 khaira777 changed the title fix(whatsapp): harden reconnect, re-pair, and sleep/wake recovery fix(whatsapp): harden reconnect and re-pair flow Oct 26, 2026
@khaira777
khaira777 enabled auto-merge (squash) October 26, 2026 23:42
@khaira777
khaira777 merged commit 54c6e80 into main Oct 26, 2026
23 checks passed
@khaira777
khaira777 deleted the fm/flocafe-1346-whatsapp-reconnect-mac-scout branch October 26, 2026 23:46
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.

bug(macos): WhatsApp reconnect flow on Mac (needs repro)

1 participant