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:
9fec9d1af4
resource: repo://src/app.rsac7a3eaa39
resource: repo://src/web_page.html7b75006986
resource: repo://src/web.rs7ea5eac45e
resource: repo://src/whatsapp.rs
generated: { by: "claude-code", at: "2026-10-23T21:26:24.156Z" }
---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.
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
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).
Appstart_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.
WaHandle::relink (src/whatsapp.rs:221-237, see
WhatsApp client):
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.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.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>.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.
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:
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.
relinking).catch_up_done), so it is not
repeated after a relink.wh with the same session.db
(README.md).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).App side (F8 confirmation, start_relink, finish_relink) and the
page script have no automated tests. See Unit tests.