A 406 from a recipient device means the server considers that device unregistered, so the cached device list we fanned out to is stale. The group path acts on that; the DM path does not.
Where it diverges
encrypt_for_devices_with_sessions_raw_detailed records the condition (wacore/src/send/encrypt.rs:708, had_unregistered_device: had_406) and both wrappers propagate it faithfully (:425, :782).
The group path reads it (wacore/src/send/group.rs:448 and :465): it feeds collect_stale_device_users, surfaces the stale users through RequiredSenderKeyDistributionError, and sets the had_unregistered_devices flag that drives the device-cache invalidation.
On the DM path nothing reads it. prepare_dm_stanza gets the summary and drops the flag, so a DM to a device the server has since dropped fans out to the stale entry, gets a 406 for it, and leaves the cache exactly as it was. The next DM to the same chat repeats the fan-out against the same stale device.
Why this is being filed and not fixed
Pre-existing behaviour, not a regression. It surfaced while reviewing #1131, which introduced encrypt_for_devices_into for the DM path; a reviewer noticed the field is now more visibly unused there. #1131 deliberately did not change it, since that PR is about allocation counts and this is a behaviour change on the send path.
What a fix would need to decide
- Whether a DM 406 should invalidate the whole user's device list, or only the offending device. The group path collects stale users, which is coarser than a DM needs.
- Whether to invalidate eagerly or on the next send.
invalidate_device_cache already exists (src/send/mod.rs:1257); the question is whether the DM path should call it inline, which costs a usync on the next send, or mark the entry so the next fan-out refreshes.
- What happens when the 406 device is the only one. The send already fails; invalidating first would make the retry meaningful instead of repeating the same fan-out.
Reproducing it
Not reproducible against the mock harness as-is: it would need a barback scenario that answers a device with a 406 while keeping the rest of the fan-out healthy, then asserts a second send re-runs usync for that user. Worth adding alongside the fix, since without it a regression here is invisible: the symptom is a wasted round trip, not an error.
A
406from a recipient device means the server considers that device unregistered, so the cached device list we fanned out to is stale. The group path acts on that; the DM path does not.Where it diverges
encrypt_for_devices_with_sessions_raw_detailedrecords the condition (wacore/src/send/encrypt.rs:708,had_unregistered_device: had_406) and both wrappers propagate it faithfully (:425,:782).The group path reads it (
wacore/src/send/group.rs:448and:465): it feedscollect_stale_device_users, surfaces the stale users throughRequiredSenderKeyDistributionError, and sets thehad_unregistered_devicesflag that drives the device-cache invalidation.On the DM path nothing reads it.
prepare_dm_stanzagets the summary and drops the flag, so a DM to a device the server has since dropped fans out to the stale entry, gets a 406 for it, and leaves the cache exactly as it was. The next DM to the same chat repeats the fan-out against the same stale device.Why this is being filed and not fixed
Pre-existing behaviour, not a regression. It surfaced while reviewing #1131, which introduced
encrypt_for_devices_intofor the DM path; a reviewer noticed the field is now more visibly unused there. #1131 deliberately did not change it, since that PR is about allocation counts and this is a behaviour change on the send path.What a fix would need to decide
invalidate_device_cachealready exists (src/send/mod.rs:1257); the question is whether the DM path should call it inline, which costs a usync on the next send, or mark the entry so the next fan-out refreshes.Reproducing it
Not reproducible against the mock harness as-is: it would need a barback scenario that answers a device with a 406 while keeping the rest of the fan-out healthy, then asserts a second send re-runs usync for that user. Worth adding alongside the fix, since without it a regression here is invisible: the symptom is a wasted round trip, not an error.