docs(authentication): document pair-code lifecycle changes (#1163) - #460
Conversation
…t-a-time whatsapp-rust#1163 decoupled the pair-code (phone-number) flow's lifetime from QR rotation: exhausted QR refs now dispatch a new Event::PairingQrCodesExhausted instead of always disconnecting, pair_with_code fails with PairCodeError::CodeAlreadyOutstanding instead of silently replacing a live code, Client::cancel_pair_code is the explicit reset, and PairingCodeRefresh now also fires on an unanswered companion_finish timeout. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhLPPFU3muB27aSGPiHrCb
|
Warning Review limit reached
Next review available in: 7 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (4)
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. Comment |
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 50efb7092b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| /// `true` when the client closed the connection itself, which it only does | ||
| /// with no pair-code flow outstanding. `false` means the socket was left | ||
| /// up and reconnecting is the consumer's call. |
There was a problem hiding this comment.
Describe
disconnected as a pending disconnect
The documented implementation dispatches this event before awaiting client_clone.disconnect() (concepts/authentication.mdx:195-201). Because CoreEventBus::dispatch invokes struct handlers inline, those handlers receive disconnected: true while the socket is still connected; closure handlers can also race the shutdown. Saying the client has already closed and advising an immediate reconnect can therefore produce a no-op or race. Describe this value as an intent to disconnect and tell consumers to wait for the subsequent disconnect before reconnecting, or emit the event after shutdown.
Useful? React with 👍 / 👎.
| ### PairingCodeRefresh | ||
|
|
||
| **Emitted:** When the server asks the companion to refresh an in-progress pair code (WA Web `refreshAltLinkingCode` / `forceManualRefresh`). Only fired while a pair-code flow is outstanding and the server's ref matches it — a `refresh_code` notification for a stale or unrelated flow is silently ignored. | ||
| **Emitted:** When the in-progress phone-number pairing code should be replaced. Covers two triggers (WA Web `Alt/DeviceLinkingApi.js` + `Link/DevicePhoneNumberCodeScreen.react.js`): the server asking for it (`refreshAltLinkingCode` / `forceManualRefresh`, only while a pair-code flow is outstanding and the server's ref matches it — a `refresh_code` notification for a stale or unrelated flow is silently ignored), and a `companion_finish` that went unanswered for a minute (`PairCodeUtils::primary_hello_pair_success_timeout()`) — a primary that could not open the key bundle just goes quiet, so silence is the only signal there is. |
There was a problem hiding this comment.
Update the Bot handler docs for the timeout trigger
This adds an unanswered-companion_finish timeout as a second PairingCodeRefresh trigger, but /api/bot.mdx:661-689 still says on_pair_code_refresh fires when the server requests a refresh, logs every callback as server-requested, and claims it only fires for a matching notification. Readers using the Bot API page therefore receive an incorrect callback contract when force_manual is false; update that page alongside these event docs.
Useful? React with 👍 / 👎.
| This can run concurrently with QR code pairing — whichever completes first wins. | ||
|
|
||
| <Note> | ||
| **One code at a time.** Fails with `PairCodeError::CodeAlreadyOutstanding` while a previously issued code is still within its validity window, instead of silently replacing it — a second code does not replace the first for the phone, since the server routes `primary_hello` by number without ever seeing the code. Call [`cancel_pair_code`](#cancel_pair_code) first when the replacement is intentional. Do not call this on a schedule driven by QR-code rotation — the two flows have unrelated lifetimes. See [One code at a time](/concepts/authentication#one-code-at-a-time). |
There was a problem hiding this comment.
Split the pair-code note into concise sentences
The opening sentence combines the failure condition, previous replacement behavior, server-routing rationale, and phone-side outcome into one long statement. Split these into one-idea sentences so the API constraint and remedy are easy to scan.
AGENTS.md reference: AGENTS.md:L25-L25
Useful? React with 👍 / 👎.
| | `PairCode(PhoneNumberTooShort)` | Fewer than 7 digits | | ||
| | `PairCode(PhoneNumberNotInternational)` | Starts with `0` (not international format) | | ||
| | `PairCode(InvalidCustomCode)` | Custom code is not 8 valid Crockford Base32 characters | | ||
| | `PairCode(CodeAlreadyOutstanding { remaining })` | A previously issued code is still within its validity window — call `cancel_pair_code` first | |
There was a problem hiding this comment.
Include pending pair-success in the outstanding-code condition
This limits CodeAlreadyOutstanding to the previous code's validity window, but the new lifecycle documentation says a flow remains outstanding while pair-success is pending, even after that window ends (concepts/authentication.mdx:205,347). For example, a primary_hello accepted near expiry starts the additional one-minute success wait, so a caller retrying immediately after the displayed timeout can still receive this error. Document the pending-confirmation case in this table and the preceding note.
Useful? React with 👍 / 👎.
| pub async fn cancel_pair_code(self: &Arc<Self>) | ||
| ``` | ||
|
|
||
| Abandons the outstanding pair-code flow, if any — the explicit reset [`pair_with_code`](#pair_with_code) requires before it will mint a replacement (WA Web's `initializeAltDeviceLinking()`). After this the previous code can no longer complete: a `primary_hello` for it is dropped rather than answered with a bundle its holder cannot open. A no-op when no flow is outstanding. |
There was a problem hiding this comment.
Limit the cancellation guarantee once stage 2 has begun
The claim that the old code can no longer complete is only safe when cancellation takes effect before its primary_hello is accepted. The lifecycle section states that stage 2 holds pair_code_state from validation through sending companion_finish (concepts/authentication.mdx:539-551), so a cancellation racing an already-processing primary_hello cannot withdraw the key bundle that has been or is about to be sent; the subsequent pair-success may still finish linking. Document this boundary instead of presenting cancellation as unconditional revocation.
Useful? React with 👍 / 👎.
…e, event ordering Fixes from automated review on #460: - CodeAlreadyOutstanding also covers a pending pair-success past the code's validity window, not just the window itself (remaining reads 0 there). - cancel_pair_code races an already-accepted primary_hello: document that it's only guaranteed effective before the phone enters the code. - PairingQrCodesExhausted.disconnected is dispatched before disconnect() is awaited — describe it as an in-progress teardown, not a completed one, and point consumers at Event::Disconnected instead. - api/bot.mdx's on_pair_code_refresh docs only described the server-refresh trigger; document the unanswered-companion_finish timeout too. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhLPPFU3muB27aSGPiHrCb
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0c763e60d0
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| See [QR ref exhaustion](/concepts/authentication#qr-ref-exhaustion) for why this no longer disconnects unconditionally. | ||
|
|
||
| <Note> | ||
| `disconnected: true` reports intent, not a completed action: the client dispatches this event *before* awaiting `disconnect()`, so the socket may still be open at the moment a handler observes it. A synchronous `EventHandler` runs inline ahead of the disconnect; a `Bot`/`on_event` closure runs off a channel on its own task and can race it either way. Treat `true` as "reconnecting shortly" and wait for [`Event::Disconnected`](#disconnected) rather than assuming the socket is already closed. |
There was a problem hiding this comment.
Stop waiting for a suppressed disconnect event
When QR refs are exhausted without an outstanding pair code, this advice waits for an event that the documented implementation does not emit: concepts/events.mdx:248 explicitly limits Disconnected to connections ending without an intentional client close, while the new snippet calls Client::disconnect(), which also disables automatic reconnection (api/client.mdx:171,460). This fresh contract evidence means a consumer following the example can wait forever and never obtain new QR refs; document an actual completion mechanism or change the implementation to notify consumers after the socket closes.
Useful? React with 👍 / 👎.
| eprintln!("Custom code must be 8 valid Crockford Base32 characters"); | ||
| } | ||
| Err(PairError::PairCode(PairCodeError::CodeAlreadyOutstanding { remaining })) => { | ||
| eprintln!("A code is already displayed, {remaining:?} left in its validity window"); |
There was a problem hiding this comment.
Do not label pending confirmation as validity time
When CodeAlreadyOutstanding is returned because an accepted primary_hello is awaiting pair-success after the code expired, remaining is deliberately zero, so this copied example prints that zero time remains in the validity window even though the live pending confirmation is what blocks the request. That misdiagnosis can prompt consumers to cancel or retry while pairing is about to complete; make the message distinguish a nonzero validity window from the zero-duration pending-confirmation case.
Useful? React with 👍 / 👎.
Two more from Codex review on #460: - The previous fix told consumers to wait for Event::Disconnected after PairingQrCodesExhausted.disconnected. That event never fires for this path: Client::disconnect() sets expected_disconnect, which is exactly what Disconnected's emission is scoped to exclude, and disconnect() also disables auto-reconnect. Replaced with: no Disconnected follows, call connect() yourself, back off on AlreadyConnected. - The CodeAlreadyOutstanding examples printed `remaining` as if it always meant validity-window time left, when it reads 0 for the pending pair-success case too — reworded so the message doesn't imply "no time left before you may retry" when the real blocker is a live confirmation. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NhLPPFU3muB27aSGPiHrCb
What
Documents the user-facing behavior/API changes from whatsapp-rust#1163, which decoupled the pair-code (phone-number linking) flow's lifetime from QR rotation.
concepts/authentication.mdxEvent::PairingQrCodesExhausted { disconnected }instead of always disconnecting — the client now only disconnects when no pair-code flow is outstanding.pair_with_codenow fails withPairCodeError::CodeAlreadyOutstanding { remaining }instead of silently replacing a live code, andClient::cancel_pair_code()is documented as the explicit reset.PairingCodeRefreshnow covers two triggers — the server'srefresh_code(as before) and a new one-minute unanswered-companion_finishtimeout.CodeAlreadyOutstandingandCancelledto thePairCodeErrormatch example, and rewrote the QR-code-errors blurb to match the new disconnect behavior.concepts/events.mdxEvent::PairingQrCodesExhausted(PairingQrCodesExhausted)to theEventenum listing and gave it its own### PairingQrCodesExhaustedsection (payload, example, and a note on why itsEventKinddiscriminant was appended at the end rather than inserted next to theEventvariant).### PairingCodeRefreshto describe both triggers.api/client.mdxCodeAlreadyOutstanding/Cancelledbehavior onpair_with_code.### cancel_pair_codesection.Not in scope
Per the task instructions, changelog entries are added by a human and are intentionally left untouched here.
Testing
npx mint broken-links— no broken links.Generated by Claude Code
Summary by cubic
Updates authentication docs for pair-code lifecycle changes and reconnect guidance. Adds
Event::PairingQrCodesExhausted, enforces one-code-at-a-time withPairCodeError::CodeAlreadyOutstanding, documentscancel_pair_code, and expandsPairingCodeRefreshto also fire after an unansweredcompanion_finish.Event::PairingQrCodesExhaustedand branch ondisconnected. It dispatches beforedisconnect()is awaited, and noEvent::Disconnectedfollows this path — callconnect()yourself when ready (back off ifConnectError::AlreadyConnected).pair_with_codeon QR rotation; callcancel_pair_code()before replacing a code. Cancellation is only guaranteed before the phone enters the code.PairCodeError::CodeAlreadyOutstandingandCancelled.remainingreflects the code’s validity window and may be0while apair-successis pending — it’s not “time left before retry.”Event::PairingCodeRefreshas coming from the server or the one‑minute unansweredcompanion_finish; the flow is cleared before the event so callingpair_with_codeagain won’t hitCodeAlreadyOutstanding(updated inapi/bot.mdx).Written for commit bbf06c8. Summary will update on new commits.