Skip to content

fix: sync own device list at login and parse server key-index in usync - #479

Merged
jlucaso1 merged 1 commit into
mainfrom
fix/sync-own-device-list-at-login
Apr 1, 2026
Merged

jlucaso1 merged 1 commit into
mainfrom
fix/sync-own-device-list-at-login

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Apr 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

DM messages were not reaching WA Web and other companion devices. Two bugs:

  1. Own device list not synced at login — WA Web calls syncMyDeviceList() during bootstrap. We weren't doing this, so companion devices added after pairing were missing from the DM fan-out.

  2. Server-returned key-index discarded — The usync parser extracted device IDs but ignored key-index attributes. Without key-index, ADV filtering removed all companion devices.

Changes

  • wacore/src/usync.rs: New UsyncDevice struct with device + key_index. Parser extracts key-index attribute. Unit test added.
  • wacore/src/iq/usync.rs: Same parser fix for the IQ-level response handler.
  • src/usync.rs: Server key_index takes priority over cached. New sync_own_device_list().
  • src/client.rs: Call sync_own_device_list() during post-login init, uses log_sync_error for shutdown suppression.

Test plan

  • test_server_returned_key_index_is_parsed — verifies key-index parsing from usync response
  • All unit tests pass
  • 0 clippy warnings
  • Verified: Synced own device list from server: 8 devices, DM fan-out includes WA Web

Summary by CodeRabbit

  • New Features

    • Added automatic device list synchronization during login to ensure local device information stays up-to-date.
  • Bug Fixes

    • Improved device key index handling to prioritize server-provided values over cached data, ensuring more accurate device configuration.

@coderabbitai

coderabbitai Bot commented Apr 1, 2026

Copy link
Copy Markdown

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Post-login startup now performs an explicit synchronization of the client's own device list: a new Client::sync_own_device_list() is added and invoked during post-login initialization under the existing generation checks. Device list parsing/storage was changed to include per-device optional key_index (UsyncDevice) and server-provided key-index attributes are now parsed.

Changes

Cohort / File(s) Summary
Post-Login Startup
src/client.rs
Replace passive-tasks placeholder with an explicit call to client_clone.sync_own_device_list().await, guarded by check_generation!() and logging errors via log_sync_error without aborting login.
Client-side Device Sync Logic
src/usync.rs
Add pub(crate) async fn sync_own_device_list(&self) -> Result<(), anyhow::Error>: read local snapshot from persistence_manager, derive target JIDs from pn/lid, invalidate device cache entries, early return if no targets, call get_user_devices(&jids) and log fetched device count. Also change get_user_devices to prefer server key_index when present.
Usync types & parsing (core)
wacore/src/usync.rs
Add pub struct UsyncDevice { device: u16, key_index: Option<u32> }; change UserDeviceList.devices from Vec<Jid>Vec<UsyncDevice>; parse optional key-index attribute on <device> nodes; update response-to-JID conversion and add unit test test_server_returned_key_index_is_parsed.
IQ-level response parsing
wacore/src/iq/usync.rs
Change per-device parsing to emit UsyncDevice { device, key_index } values (read optional key-index attribute parsed as u32) instead of cloning per-device JIDs.

Sequence Diagram(s)

sequenceDiagram
    rect rgba(200,230,255,0.5)
    participant Client
    end
    rect rgba(220,255,220,0.5)
    participant PersistenceMgr
    end
    rect rgba(255,230,240,0.5)
    participant UsyncServer
    end

    Client->>Client: check_generation!()
    Client->>PersistenceMgr: read own device snapshot
    PersistenceMgr-->>Client: local snapshot (pn/lid)
    Client->>Client: derive target JIDs (pn,lid → non-ad)
    Client->>Client: invalidate device cache entries
    alt no targets
        Client->>Client: return early
    else has targets
        Client->>UsyncServer: get_user_devices(jids)
        UsyncServer-->>Client: UserDeviceList with UsyncDevice{device, key_index?}
        Client->>Client: update caches/log fetched count
        Client->>Client: log_sync_error on non-fatal errors
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

breaking-change

Poem

🐰 I hopped into login’s busy nest,

Cleared old caches, gave caches a rest,
Servers whispered device-index lore,
I fetched, I logged, then hopped out the door—
New keys, new hops, syncs evermore 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: syncing own device list at login and parsing server key-index in usync responses.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sync-own-device-list-at-login

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 and usage tips.

@github-actions

github-actions Bot commented Apr 1, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchfix/sync-own-device-list-at-login
Testbedubuntu-latest

🚨 1 Alert

BenchmarkMeasure
Units
ViewBenchmark Result
(Result Δ%)
Upper Boundary
(Limit %)
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions()Instructions
instructions x 1e3
📈 plot
🚷 threshold
🚨 alert (🔔)
47.13 x 1e3
(+8.58%)Baseline: 43.41 x 1e3
45.58 x 1e3
(103.41%)

Click to view all benchmark results
BenchmarkInstructionsBenchmark Result
instructions
(Result Δ%)
Upper Boundary
instructions
(Limit %)
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled()📈 view plot
🚷 view threshold
6,197.00
(-4.17%)Baseline: 6,466.65
6,789.99
(91.27%)
binary_benchmark::child_iteration_group::bench_get_children_by_tag📈 view plot
🚷 view threshold
524,304.00
(-25.72%)Baseline: 705,861.15
741,154.21
(70.74%)
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled()📈 view plot
🚷 view threshold
20,868.00
(-5.30%)Baseline: 22,036.44
23,138.26
(90.19%)
binary_benchmark::marshal_group::bench_marshal_allocating📈 view plot
🚷 view threshold
98,202.00
(-14.12%)Baseline: 114,347.53
120,064.91
(81.79%)
binary_benchmark::marshal_group::bench_marshal_auto_allocating📈 view plot
🚷 view threshold
98,230.00
(-9.39%)Baseline: 108,405.54
113,825.82
(86.30%)
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating📈 view plot
🚷 view threshold
532,948.00
(-0.10%)Baseline: 533,478.11
560,152.01
(95.14%)
binary_benchmark::marshal_group::bench_marshal_auto_long_string📈 view plot
🚷 view threshold
15,870.00
(-4.35%)Baseline: 16,592.11
17,421.72
(91.09%)
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating📈 view plot
🚷 view threshold
14,715,201.00
(-7.40%)Baseline: 15,890,831.13
16,685,372.69
(88.19%)
binary_benchmark::marshal_group::bench_marshal_exact_allocating📈 view plot
🚷 view threshold
118,358.00
(-19.26%)Baseline: 146,595.24
153,925.01
(76.89%)
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating📈 view plot
🚷 view threshold
534,378.00
(-0.10%)Baseline: 534,899.71
561,644.69
(95.15%)
binary_benchmark::marshal_group::bench_marshal_exact_long_string📈 view plot
🚷 view threshold
17,919.00
(-3.88%)Baseline: 18,643.23
19,575.40
(91.54%)
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating📈 view plot
🚷 view threshold
28,066,347.00
(-20.24%)Baseline: 35,187,732.24
36,947,118.86
(75.96%)
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating📈 view plot
🚷 view threshold
533,387.00
(-0.10%)Baseline: 533,917.11
560,612.96
(95.14%)
binary_benchmark::marshal_group::bench_marshal_long_string📈 view plot
🚷 view threshold
15,843.00
(-6.91%)Baseline: 17,019.35
17,870.32
(88.66%)
binary_benchmark::marshal_group::bench_marshal_many_children_allocating📈 view plot
🚷 view threshold
14,716,627.00
(-7.40%)Baseline: 15,891,976.68
16,686,575.51
(88.19%)
binary_benchmark::marshal_group::bench_marshal_reusing_buffer📈 view plot
🚷 view threshold
107,945.00
(-11.13%)Baseline: 121,464.57
127,537.80
(84.64%)
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer📈 view plot
🚷 view threshold
98,302.00
(-9.38%)Baseline: 108,477.54
113,901.42
(86.30%)
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled()📈 view plot
🚷 view threshold
90,974.00
(-5.05%)Baseline: 95,810.55
100,601.07
(90.43%)
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled()📈 view plot
🚷 view threshold
7,378.00
(-3.25%)Baseline: 7,625.53
8,006.81
(92.15%)
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled()📈 view plot
🚷 view threshold
91,005.00
(-1.65%)Baseline: 92,532.62
97,159.25
(93.67%)
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled()📈 view plot
🚷 view threshold
7,401.00
(+0.33%)Baseline: 7,376.46
7,745.29
(95.55%)
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled()📈 view plot
🚷 view threshold
106,790.00
(-1.41%)Baseline: 108,317.62
113,733.50
(93.89%)
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled()📈 view plot
🚷 view threshold
8,913.00
(+0.28%)Baseline: 8,888.46
9,332.89
(95.50%)
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled()📈 view plot
🚷 view threshold
41,989.00
(-7.06%)Baseline: 45,179.73
47,438.72
(88.51%)
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled()📈 view plot
🚷 view threshold
2,717.00
(-2.43%)Baseline: 2,784.67
2,923.91
(92.92%)
binary_benchmark::unpack_group::bench_unpack_compressed📈 view plot
🚷 view threshold
556,092.00
(+1.31%)Baseline: 548,891.09
576,335.64
(96.49%)
binary_benchmark::unpack_group::bench_unpack_uncompressed📈 view plot
🚷 view threshold
771.00
(-0.24%)Baseline: 772.85
811.49
(95.01%)
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data()📈 view plot
🚷 view threshold
27,751,923.00
(+0.19%)Baseline: 27,699,296.64
29,084,261.48
(95.42%)
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message()📈 view plot
🚷 view threshold
5,544,828.00
(-0.05%)Baseline: 5,547,713.97
5,825,099.67
(95.19%)
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session()📈 view plot
🚷 view threshold
175,061.00
(-1.27%)Baseline: 177,319.91
186,185.90
(94.02%)
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session()📈 view plot
🚷 view threshold
175,710.00
(-1.34%)Baseline: 178,090.60
186,995.13
(93.97%)
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users()📈 view plot
🚷 view threshold
17,279,380.00
(-0.02%)Baseline: 17,282,043.44
18,146,145.61
(95.22%)
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender()📈 view plot
🚷 view threshold
298,417.00
(+0.53%)Baseline: 296,844.36
311,686.58
(95.74%)
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message()📈 view plot
🚷 view threshold
12,552,524.00
(-0.34%)Baseline: 12,595,061.16
13,224,814.21
(94.92%)
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution()📈 view plot
🚷 view threshold
719,597.00
(+0.36%)Baseline: 716,988.04
752,837.44
(95.58%)
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions()📈 view plot
🚷 view threshold
🚨 view alert (🔔)
47,129.00
(+8.58%)Baseline: 43,405.36
45,575.63
(103.41%)

libsignal_benchmark::session_optimization_group::bench_message_key_eviction eviction:setup_message_key_eviction()📈 view plot
🚷 view threshold
15,561,842.00
(+0.00%)Baseline: 15,561,768.30
16,339,856.71
(95.24%)
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages()📈 view plot
🚷 view threshold
5,378,778.00
(-1.74%)Baseline: 5,474,153.75
5,747,861.43
(93.58%)
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session()📈 view plot
🚷 view threshold
312,188.00
(-60.29%)Baseline: 786,241.77
825,553.86
(37.82%)
libsignal_benchmark::signature_group::bench_key_generation keygen📈 view plot
🚷 view threshold
2,830,547.00
(+0.17%)Baseline: 2,825,717.28
2,967,003.15
(95.40%)
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message()📈 view plot
🚷 view threshold
3,452,844.00
(-0.50%)Baseline: 3,470,296.39
3,643,811.21
(94.76%)
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message()📈 view plot
🚷 view threshold
127,218,308.00
(+1.48%)Baseline: 125,363,497.57
131,631,672.44
(96.65%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message()📈 view plot
🚷 view threshold
12,002.00
(+1.45%)Baseline: 11,830.49
12,422.01
(96.62%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message()📈 view plot
🚷 view threshold
3,930.00
(+2.35%)Baseline: 3,839.84
4,031.83
(97.47%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended()📈 view plot
🚷 view threshold
87,073.00
(-0.75%)Baseline: 87,730.50
92,117.03
(94.52%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple()📈 view plot
🚷 view threshold
79,022.00
(-0.93%)Baseline: 79,762.02
83,750.12
(94.35%)
reporting_token_benchmark::key_derivation_group::bench_key_derivation📈 view plot
🚷 view threshold
50,375.00
(-1.01%)Baseline: 50,891.11
53,435.67
(94.27%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message()📈 view plot
🚷 view threshold
5,956.00
(+2.98%)Baseline: 5,783.40
6,072.57
(98.08%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message()📈 view plot
🚷 view threshold
2,227.00
(+4.25%)Baseline: 2,136.21
2,243.02
(99.29%)
reporting_token_benchmark::token_calculation_group::bench_token_calculation📈 view plot
🚷 view threshold
21,920.00
(-0.00%)Baseline: 21,920.23
23,016.24
(95.24%)
🐰 View full continuous benchmarking report in Bencher

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/client.rs`:
- Around line 1949-1951: Replace the raw warn! call used after
client_clone.sync_own_device_list().await with the existing helper
log_sync_error() so the sync failure logs follow the same reconnect/shutdown
suppression as other startup syncs; locate the block where you call
client_clone.sync_own_device_list().await and change the error branch to call
log_sync_error("sync_own_device_list", &e) (or the project’s log_sync_error
signature) instead of warn!("Failed to sync own device list: {e:?}").

In `@src/usync.rs`:
- Around line 170-183: The current sequence calls
invalidate_device_cache(&pn_bare.user) / invalidate_device_cache(&lid_bare.user)
before calling get_user_devices(&jids), which deletes the persisted
DeviceListRecord and loses key_index and raw_id metadata that get_user_devices
expects to merge; to fix, avoid deleting the persisted record before refresh:
either (A) move calls to invalidate_device_cache so they happen after devices =
self.get_user_devices(&jids).await? or (B) snapshot the existing
DeviceListRecord (read prior record for each user) before
invalidate_device_cache and after devices = self.get_user_devices(&jids).await
merge the saved key_index and raw_id back into the returned DeviceListRecord
entries; locate and update the invalidate_device_cache, get_user_devices, and
the device_snapshot / jids logic to implement one of these approaches.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: d5c628ca-a8bb-4912-b0fb-8cf9281f5d77

📥 Commits

Reviewing files that changed from the base of the PR and between 78075fc and 2c460d4.

📒 Files selected for processing (2)
  • src/client.rs
  • src/usync.rs

Comment thread src/client.rs
Comment thread src/usync.rs
Comment on lines +170 to +183
self.invalidate_device_cache(&pn_bare.user).await;
jids.push(pn_bare);
}
if let Some(ref lid) = device_snapshot.lid {
let lid_bare = lid.to_non_ad();
self.invalidate_device_cache(&lid_bare.user).await;
jids.push(lid_bare);
}

if jids.is_empty() {
return Ok(());
}

let devices = self.get_user_devices(&jids).await?;

@coderabbitai coderabbitai Bot Apr 1, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

This refresh discards the metadata source that get_user_devices() uses to preserve key_index and raw_id.

get_user_devices() merges the previous DeviceListRecord so key_index values learned via account_sync and the prior raw_id survive usync responses that omit key_index_bytes. invalidate_device_cache() deletes that persisted record first, so this login sync can rewrite our own device list without that metadata unless the server happens to resend it. Force a network refresh without deleting the previous record first, or snapshot and merge the old key_index/raw_id fields back into the refreshed result.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/usync.rs` around lines 170 - 183, The current sequence calls
invalidate_device_cache(&pn_bare.user) / invalidate_device_cache(&lid_bare.user)
before calling get_user_devices(&jids), which deletes the persisted
DeviceListRecord and loses key_index and raw_id metadata that get_user_devices
expects to merge; to fix, avoid deleting the persisted record before refresh:
either (A) move calls to invalidate_device_cache so they happen after devices =
self.get_user_devices(&jids).await? or (B) snapshot the existing
DeviceListRecord (read prior record for each user) before
invalidate_device_cache and after devices = self.get_user_devices(&jids).await
merge the saved key_index and raw_id back into the returned DeviceListRecord
entries; locate and update the invalidate_device_cache, get_user_devices, and
the device_snapshot / jids logic to implement one of these approaches.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It deletes from DB at line 186. This means get_user_devices won't find the old record in DB either → existing_key_indices will be empty → key_index
falls back to None for devices that the server doesn't return key-index for.

But now that we parse key-index from the server response (our fix), this is actually fine — the server provides key-index for companion devices. The
only case where the old cached key_index matters is device 0 (which has no key-index attribute in the response), but device 0 is always kept by
filter_devices_by_key_index regardless of key_index.

Let me verify by checking the actual server response from the logs:

← no key-index (always kept)
← server provides it
← server provides it
← server provides it

Server provides key-index for all companion devices. The existing_key_indices fallback is only for device 0, which doesn't need it. The invalidation
before fetch is fine with our key-index parsing fix. Skip.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jlucaso1, that's a solid analysis and the log evidence makes it clear — thanks for walking through it.

To summarize the reasoning for posterity:

  • Device 0 — server never sends key-index, but filter_devices_by_key_index unconditionally retains it, so no fallback to the cached value is needed.
  • Companion devices — the server includes key-index directly in the usync response (e.g., key-index="93"), so existing_key_indices is never needed for them either.
  • raw_id — already handled by the identity-change detection path and only reused when key_index_bytes is absent; for a full own-device sync the server sends key_index_bytes, so raw_id is decoded fresh.

The invalidation-before-fetch sequence is therefore safe given that the server supplies all necessary metadata in the response. I'll withdraw the concern.


✏️ Learnings added
Learnt from: jlucaso1
Repo: jlucaso1/whatsapp-rust PR: 479
File: src/usync.rs:173-186
Timestamp: 2026-04-01T22:10:15.983Z
Learning: In `src/usync.rs` (`Client::sync_own_device_list`), calling `invalidate_device_cache` before `get_user_devices` for the own-JID refresh is intentional and safe. The WhatsApp server always provides `key-index` attributes for companion devices (device id > 0) in the usync response. Device 0 never has a `key-index` attribute but is unconditionally kept by `filter_devices_by_key_index`, so the `existing_key_indices` fallback in `get_user_devices` is never needed for the own-device-list sync case.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

@jlucaso1
jlucaso1 force-pushed the fix/sync-own-device-list-at-login branch from 2c460d4 to 6507182 Compare April 1, 2026 22:08
Two bugs fixed:
1. Own device list not synced at login (WA Web calls syncMyDeviceList
   during bootstrap). Without this, companion devices added after initial
   pairing were missing from the DM fan-out.
2. Server-returned key-index attributes on <device> nodes were discarded
   by the usync parser, causing ADV filtering to remove companion devices
   that had key_index=None in the stale cache.

Changes:
- New UsyncDevice struct carries device + key_index from server response
- Usync parser extracts key-index attribute from <device> nodes
- Server key_index takes priority over cached value in device list processing
- New sync_own_device_list() called during post-login init
@jlucaso1
jlucaso1 force-pushed the fix/sync-own-device-list-at-login branch from 6507182 to 6fe4c35 Compare April 1, 2026 22:09
@jlucaso1 jlucaso1 changed the title fix: sync own device list at login so DM fan-out includes all companions fix: sync own device list at login and parse server key-index in usync Apr 1, 2026
@jlucaso1

jlucaso1 commented Apr 1, 2026

Copy link
Copy Markdown
Collaborator Author

@claude review

@claude

claude Bot commented Apr 1, 2026

Copy link
Copy Markdown

Claude finished @jlucaso1's task in 1m 7s —— View job


Code Review: PR #479 — Sync own device list at login

  • Read changed files and understand the diff
  • Review wacore/src/usync.rs changes
  • Review wacore/src/iq/usync.rs changes
  • Review src/usync.rs changes
  • Review src/client.rs changes
  • Post review findings

Overall this is a clean, well-scoped fix. The two bugs are clearly identified, the fix is minimal, and the test covers the core parsing change. A few observations:

wacore/src/usync.rsUsyncDevice struct + parser fix

Looks good. The new UsyncDevice struct cleanly separates device ID from key-index, and the parser correctly extracts key-index as an Option<u32>. The test (test_server_returned_key_index_is_parsed) is thorough and mirrors real server output. No issues.

wacore/src/iq/usync.rs — IQ-level parser fix

Same pattern applied consistently. The has_companion check at line 623 correctly uses d.device on the new UsyncDevice type. Clean.

src/usync.rssync_own_device_list() + server key_index priority

The key_index priority change (lines 122-128) is correct — server-returned values take precedence, falling back to cached values via or_else. This is the right merge strategy.

sync_own_device_list() is straightforward. The get_user_devices call handles cache update internally, so the return value doesn't need to be used beyond the log line.

One minor note: the removed comment block // === Passive Tasks (mimics WhatsApp Web's PassiveTaskManager) === was contextually useful. The new code still sits in the passive tasks section, and the comment helped orient readers. Consider keeping a brief version of it (not blocking).

src/client.rs — Login bootstrap integration

Good placement — syncing before passive tasks (pre-key upload) ensures device list is fresh before any fan-out. The check_generation!() guard and log_sync_error usage are consistent with surrounding code. I see the CodeRabbit suggestion to use log_sync_error was already applied — nice.

Benchmark alert

The Bencher alert on bench_decrypt_with_previous_session (+8.58%) is unrelated to this change — it touches no crypto/session code. Likely CI noise.

Verdict: LGTM — clean fix, good test, consistent patterns. Ship it.

@jlucaso1
jlucaso1 merged commit d3abc50 into main Apr 1, 2026
11 of 12 checks passed
@jlucaso1
jlucaso1 deleted the fix/sync-own-device-list-at-login branch April 1, 2026 22:16

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6fe4c35496

ℹ️ 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".

Comment thread src/usync.rs
Comment on lines +173 to +174
self.invalidate_device_cache(&pn_bare.user).await;
jids.push(pn_bare);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep existing own-device cache until refresh succeeds

sync_own_device_list() deletes the persisted/cache device record before attempting the network usync, so a transient failure during that fetch (e.g., reconnect race or IQ error) leaves the account with no local own-device list. Because DM send later does get_user_devices(own_jid)?, this turns a best-effort startup sync into a hard send failure path whenever the refetch also fails, instead of falling back to the previously known devices. Consider only replacing/invalidation after a successful fetch, or restoring old data on failure.

Useful? React with 👍 / 👎.

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.

1 participant