Skip to content

Sync exchange v2 update pixels for more granularity#9104

Open
CDRussell wants to merge 1 commit into
feature/craig/fix/sync_exchange_v2_tidy_terminal_statesfrom
feature/craig/sync_exchange_v2_update_pixels
Open

Sync exchange v2 update pixels for more granularity#9104
CDRussell wants to merge 1 commit into
feature/craig/fix/sync_exchange_v2_tidy_terminal_statesfrom
feature/craig/sync_exchange_v2_update_pixels

Conversation

@CDRussell

@CDRussell CDRussell commented Jul 3, 2026

Copy link
Copy Markdown
Member

Task/Issue URL: https://app.asana.com/1/137249556945/project/608920331025315/task/1216244452632489?focus=true
Tech Design URL (if applicable):

Description

Adds granular reason classification to the v2 sync setup failure pixels, matching the canonical vocabulary in the [Exchange V2 Unified Algorithm spec](https://app.asana.com/1/137249556945/task/1214739740392701).

Previously sync_setup_ended_failed was emitted with only broad reasons (transport_failure, unexpected_failure), making it hard to tell a benign network blip from a protocol violation or a peer-side problem.

This PR:

  • Adds SessionErrorKind on ExchangeV2Event.SessionError so the runner classifies transport, protocol, and preparation errors distinctly at the source.
  • Extends the sync_setup_ended_failed enum with the spec's canonical reasons
  • Adds a timeout_stage parameter to sync_setup_ended_failed, sent only when reason=session_timeout
  • Adds a reason parameter to the parse-error pixels

Steps to test this PR

Note

Requires the Python pairing reference client for the broken-mode scenarios.

Logcat filter (Android Studio format):

message~:"Pixel sent: sync_setup"

For each scenario below, start with no existing sync account.

1. unrecognized_code — simplest, no companion client needed

Verifies the new reason parameter on the manual-code parse-error pixel.

  • On Android: Sync settings → Sync With Another Device → Manually Enter Code.
  • Paste any nonsense string (e.g. not-a-sync-code) and confirm.
  • Expected pixel: Pixel sent: sync_setup_manual_code_entered_failed with params: {..., reason=unrecognized_code, flow_version=v2, ...}

2. unexpected_second_hello — protocol violation classification

Verifies the new precise reason for second/duplicate hello (previously would have surfaced as a generic transport failure).

Direction: Android = Presenter (shows code), Python = Scanner.

  • On Android: Sync settings → Sync With Another Device → Copy Text Code.
  • In a terminal:
    poetry run pairing-client \
      --server-url https://sync.duckduckgo.com/ \
      --broken-mode duplicate-hello \
      --kind 3party
    
  • When Python prompts Paste the other device's code:, paste Android's code.
  • Expected pixel: Pixel sent: sync_setup_ended_failed with params: {..., reason=unexpected_second_hello, ...}

3. unexpected_event — out-of-order message classification

Verifies that a non-hello message arriving before hello (SM implicit abort from Bootstrapped) surfaces as unexpected_event rather than the previous generic bucket.

Direction: Android = Presenter, Python = Scanner.

  • Android: same as scenario 2 — Sync With Another Device → Copy Text Code.
  • In a terminal:
    poetry run pairing-client \
      --server-url https://sync.duckduckgo.com/ \
      --broken-mode response-without-hello \
      --kind 3party
    
  • Paste Android's code into Python when prompted.
  • Expected pixel: Pixel sent: sync_setup_ended_failed with params: {..., reason=unexpected_event, ...}

4. (Optional, long-running) session_timeout + timeout_stage=waiting_for_peer_hello

Verifies the new timeout_stage sub-parameter. Skip if you don't want to wait 5 minutes — the mapping is exercised by unit tests in SyncPixelsTest.

  • Android: Sync settings → Sync With Another Device → Copy Text Code.
  • Leave the phone on the code screen for 5 minutes. Don't scan the code anywhere.
  • Expected pixel: Pixel sent: sync_setup_ended_failed with params: {..., reason=session_timeout, timeout_stage=waiting_for_peer_hello, ...}

UI changes

None. Error dialogs and copy are unchanged; only the pixel payloads gain more granular parameters.


Note

Medium Risk
Changes v2 pairing error handling and poll-loop behavior (terminal throws vs silent stop), which could alter when sessions fail or retry; risk is mitigated by extensive unit tests and telemetry-only user impact.

Overview
Aligns v2 sync setup telemetry with the Exchange V2 Unified Algorithm spec by classifying failures at the source and emitting richer pixel payloads.

Exchange v2 runner and relay: SessionError now carries SessionErrorKind and optional TimeoutStage (for 5‑minute deadlines). Poll/send HTTP outcomes throw typed exceptions (ChannelGone, PollBadRequest, PollAuthDenied) instead of silently stopping the poll loop, so failures surface as specific session errors (e.g. relay unavailable, malformed request, unexpected second hello, pairing session not ready).

Dispatcher → pixels: RealSyncCodeDispatcher maps each SessionErrorKind to dedicated AccountErrorCodes (including SESSION_TIMEOUT, PEER_RECOVERY_CODE_UNAVAILABLE, etc.) and passes timeoutStage on DispatchOutcome.Failed. joinAccountFromThirdPartyRecoveryCode upgrade POST failures now use ACCOUNT_UPGRADE_FAILED.

Pixels: sync_setup.json5 gains reason on barcode/manual parse failures and expands sync_setup_ended_failed with the spec reason enum plus timeout_stage. SyncPixels adds matching SetupFailureReason / TimeoutStage, includes source on setup-failed events, fires already_paired for same-account AlreadyConnected, treats peer PAIRING_REJECTED as abandoned confirmation denial, and wires connect/exchange/login ViewModels to pass screen type and unrecognized_code on parse failures.

Reviewed by Cursor Bugbot for commit abdd812. Configure here.

Copy link
Copy Markdown
Member Author

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@CDRussell CDRussell force-pushed the feature/craig/fix/sync_exchange_v2_tidy_terminal_states branch from f78e252 to f94c91c Compare July 3, 2026 18:33
@CDRussell CDRussell force-pushed the feature/craig/sync_exchange_v2_update_pixels branch from 451a7d9 to 61fb264 Compare July 3, 2026 18:33
@CDRussell CDRussell force-pushed the feature/craig/fix/sync_exchange_v2_tidy_terminal_states branch from f94c91c to 9255d4f Compare July 3, 2026 23:25
@CDRussell CDRussell force-pushed the feature/craig/sync_exchange_v2_update_pixels branch 2 times, most recently from 92791e8 to 08ed2cf Compare July 4, 2026 00:19
@CDRussell CDRussell force-pushed the feature/craig/sync_exchange_v2_update_pixels branch from 08ed2cf to abdd812 Compare July 4, 2026 00:58
@CDRussell CDRussell marked this pull request as ready for review July 4, 2026 01:03
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Privacy Review task: https://app.asana.com/0/69071770703008/1216272438450758

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants