relink-and-pairing.md 9.8 KB


type: workflow title: WhatsApp relink and pairing description: End-to-end trace of pairing wh as a WhatsApp linked device and of re-pairing without restarting — F8 twice in the TUI or "Nuovo QR" in the web panel, App::start_relink, WaHandle::relink (logout, stop, session.db deletion or in-place reset, new unpaired client), the new QR on the TUI pairing screen and in the web pairing card, failure behaviour and tests. tags: [whatsapp, pairing, relink, qr-code, session, web-panel, tui] verified:

  • by: openwiki/0.7.2 at: 2026-10-23T21:26:24.156Z sources:
  • id: openwiki-source-9fec9d1af4 resource: repo://src/app.rs
  • id: openwiki-source-ac7a3eaa39 resource: repo://src/web_page.html
  • id: openwiki-source-7b75006986 resource: repo://src/web.rs
  • id: openwiki-source-7ea5eac45e resource: repo://src/whatsapp.rs generated: { by: "claude-code", at: "2026-10-23T21:26:24.156Z" } ---

WhatsApp relink and pairing

wh is a WhatsApp linked device: it pairs by QR code and keeps the device credentials in session.db (see Configuration and data). Pairing happens on first start, and can be redone at any time without restarting the process — to link another account, or after the QR codes expired — with F8 pressed twice in the TUI or the "Nuovo QR" button of the web control panel.

Flow

TUI: F8 -> confirm prompt -> F8          Web: "Nuovo QR" -> confirm() -> POST /api/relink
            (Esc / other key cancels)                         (X-Wh-Request: 1)
                     \                                         /
                      v                                       v
             App::request_relink                   App::handle_web_request(Relink)
                      \                                       /
                       +--------> App::start_relink <--------+
                                   (ignored while relinking)
                                   screen = Connecting, qr cleared,
                                   status "Cancellazione della sessione in corso...",
                                   own_jids_known = false
                                          |
                                   tokio task: WaHandle::relink
                                     1. take session out of the slot
                                     2. Session::stop: logout, wait <= 10 s, abort translator
                                     3. reset_session_files: delete session.db (+ -wal, -shm)
                                          or, if locked, empty its tables in place
                                     4. open_session: new unpaired client on the same events channel
                                          |
                                   AppEvent::Relinked(result) -> App::finish_relink (status)
                                          |
                     new client -> WaEvent::Qr -> Screen::Pairing, qr_code stored
                                          |                                   \
                     TUI pairing screen (half-block QR)        GET /api/state: screen "pairing",
                                          |                    qr_svg -> web pairing card
                     phone scans -> WaEvent::Connected -> Screen::Main, own JIDs refreshed

1. Requesting a relink

TUI. F8 works on every screen (connecting, pairing and main), so a stuck or expired pairing can be restarted from the pairing screen itself, whose key help reads F8: nuovo QR F10: esci (Terminal UI). handle_key checks F8 right after F10 (src/app.rs:884-903). request_relink (src/app.rs:356-369) cancels a pending chat deletion; the first press sets pending_relink and shows Cancellare la sessione WhatsApp e rifare l'abbinamento? F8 conferma, Esc annulla; the second press calls start_relink. Any other key in between clears the flag through cancel_pending_relink (src/app.rs:371-381) with the status Nuovo abbinamento annullato; an Esc used that way does nothing else, other keys are then handled normally.

Web panel. "Nuovo QR" (relinkBot, src/web_page.html:703-718) asks Cancellare la sessione WhatsApp e rifare l'abbinamento? Il dispositivo verrà scollegato dal telefono. and posts /api/relink with the mandatory X-Wh-Request: 1 header (src/web.rs:166-174, src/web.rs:238-254). App::handle_web_request maps WebCommand::Relink straight to start_relink — the browser dialog replaces the double F8 (Application state).

2. Starting it in App

start_relink (src/app.rs:320-341) clears pending_relink and returns at once if relinking is already set, so repeated requests during a relink are ignored. Otherwise it sets relinking, switches to Screen::Connecting, drops the stored QR, shows Cancellazione della sessione in corso... and resets own_jids_known, because the next pairing may be a different account and the autoreply gate must learn its JIDs again. It then spawns a tokio task that awaits WaHandle::relink and sends AppEvent::Relinked with Ok(()) or the error text.

The web request returns the state snapshot taken right after start_relink, i.e. screen: "connecting" and no QR; the page shows its pairing card with "Connessione a WhatsApp in corso..." and starts polling every 2 s.

3. Replacing the session

WaHandle::relink (src/whatsapp.rs:221-237, see WhatsApp client):

  1. Take the current Session out of the shared slot. From now until step 4 succeeds every client operation fails with WhatsApp session not running: manual and AI sends end with Invio fallito: ..., image downloads fail, own_jids is empty.
  2. Stop it (Session::stop, src/whatsapp.rs:182-198): logout deregisters the device on the phone when connected (best effort) and disconnects; then it waits up to STOP_TIMEOUT_SECONDS (10 s) for the client run loop to report it stopped, and aborts the translator task, which holds a reference to the client.
  3. Reset the session database (reset_session_files, src/whatsapp.rs:314-329): delete session.db and its -wal / -shm side files. If the deletion fails — on Windows whatsapp-rust keeps the SQLite file open until the process exits — clear_session_tables (src/whatsapp.rs:349-367) opens it with rusqlite (5 s busy timeout) and, in one transaction, deletes every row of every table except SQLite internals and __diesel_schema_migrations, so the schema stays valid for the next client. If both fail the error reads cannot delete (<io error>) nor clear session database <path>.
  4. Start a new client on the empty database with open_session (src/whatsapp.rs:369-416) and put it in the slot. It sends its events to the same WaEvent channel as the old one, so the forwarder and App need no rewiring.

Only session.db is reset. history.db — chats, messages, contacts, autoreply flags and image descriptions — is untouched, so chats of the previously linked account stay visible until deleted locally.

4. Outcome and new QR

finish_relink (src/app.rs:343-354) clears relinking and shows Sessione cancellata: scansiona il nuovo QR or Nuovo abbinamento fallito: <error>. After a failure the handle has no session: the app stays on the connecting screen and the user can press F8 twice again (or restart).

The new client emits WaEvent::Qr payloads; handle_wa_event switches to Screen::Pairing and stores the payload (src/app.rs:406-411). It is shown in two places:

  • the TUI pairing screen draws it with half-block characters (qr_to_text), or a "terminal too small" message (Terminal UI);
  • App::web_state renders it to SVG with web::qr_svg (src/web.rs:364-379) and returns it as qr_svg with screen: "pairing"; the page's pairing card shows it as an image and the WhatsApp linking steps, polling every 2 s so rotated codes appear quickly (src/web_page.html:596-628).

When the library runs out of QR codes the status shows pairing QR codes exhausted; request a new QR to pair again — another relink starts a fresh sequence. Scanning the code produces WaEvent::Connected: the app switches to Screen::Main, clears the QR, shows Connesso and fetches the new account's JIDs; the web page hides the pairing card and returns to 5 s polling.

Failure and edge cases

  • A relink requested while another runs is ignored (relinking).
  • If the old client does not stop within 10 s the relink continues anyway; on a still-open database the in-place reset is used.
  • The startup catch-up runs once per process (catch_up_done), so it is not repeated after a relink.
  • The web panel is unauthenticated: anyone reaching the port can unlink the device and, while pairing, scan the QR to link their own phone (see Web control panel).
  • Never run two copies of wh with the same session.db (README.md).

Tests

  • web.rs: POST /api/relink resolves to the relink route, requires the X-Wh-Request header, and GET /api/relink is not found; qr_svg produces an SVG document (src/web.rs:422-442).
  • whatsapp.rs: the ignored live test relink_deletes_the_session_and_offers_a_new_qr (src/whatsapp.rs:1163-1183) starts a real client, waits for a QR, relinks, waits for the next QR and checks that the device keys in the two payloads differ; run it with cargo test -- --ignored (network needed).
  • The App side (F8 confirmation, start_relink, finish_relink) and the page script have no automated tests. See Unit tests.