Skip to content

fix(retry): match WA Web's bot gate so bot DM retry receipts aren't dropped - #705

Merged
jlucaso1 merged 1 commit into
mainfrom
fix/bot-dm-retry-receipt-gate
Jun 4, 2026
Merged

jlucaso1 merged 1 commit into
mainfrom
fix/bot-dm-retry-receipt-gate

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jun 4, 2026

Copy link
Copy Markdown
Collaborator

Problem

send_retry_receipt suppressed the retry receipt whenever !we_are_bot && sender_is_bot, i.e. any time the message sender is a bot and our own account is not. For a 1:1 chat with a bot (Meta AI, etc.), a message we failed to decrypt is then silently never recovered: send_retry_receipt returns Ok(()) early, run_retry_receipt treats it as sent, the transport ack clears the stanza from the server queue, and no <receipt type="retry"> is ever emitted.

WA Web's sendRetryReceipt aborts on a different condition: !to.isBot() && participant.isBot(), where to is the sender (getFrom) and participant is null for DMs (MsgSendReceipt passes participant = type === CHAT ? null : ...). So for a bot DM participant is null, the abort never fires, and WA Web sends the retry. Our we_are_bot term has no counterpart in WA Web's condition at all.

Fix

Replace the hand-rolled gate with the existing MessageSource::is_bot_authored_non_bot_chat() helper, which already encodes WA Web's !chat.isBot() && author.isBot() and is already used on the success/ack path (message.rs) and the self-fanout decrypt-failure path:

if info.source.is_bot_authored_non_bot_chat() { return Ok(()); }
  • Bot DM: chat == sender == bot, so !chat.is_bot() && sender.is_bot() is false, the retry is sent (matches WA Web).
  • Bot reply in a non-bot group: chat is the group (non-bot), sender is the bot, so it is suppressed (matches WA Web).
  • This also drops the WA-Web-less we_are_bot term and makes the retry path consistent with the ack and self-fanout paths, which already route through the same helper.

Tests

New is_bot_authored_non_bot_chat_matches_wa_web locks the gate semantics: bot DM not suppressed, group bot reply suppressed, normal user DM not suppressed.

cargo fmt --all
cargo clippy -p whatsapp-rust -p wacore --all-targets -- -D warnings   # clean
cargo test -p whatsapp-rust --lib   # 669 pass
cargo test -p wacore                # pass (incl. 1 new)

Breaking

None. Internal gate change; the only behavior change is that bot DM retry receipts are now sent (and a local bot account no longer over-sends in groups).

…ropped

send_retry_receipt suppressed the retry whenever the sender is a bot and we are not (!we_are_bot && sender_is_bot). For a bot DM that means a message we failed to decrypt is silently never recovered. WA Web aborts only on !to.isBot() && participant.isBot(), and participant is null for DMs, so it always sends the retry for a bot DM.

Use the existing MessageSource::is_bot_authored_non_bot_chat() helper (!chat.is_bot() && sender.is_bot()), already used by the ack and self-fanout paths, which suppresses a bot reply in a non-bot group but not a bot DM.
@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 256c1aed-e533-4b82-b1da-c10e4f921b94

📥 Commits

Reviewing files that changed from the base of the PR and between 93bb7fe and 393f67e.

📒 Files selected for processing (2)
  • src/retry.rs
  • wacore/src/types/message.rs

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Aligned retry receipt handling with WhatsApp Web's bot-filtering behavior for improved consistency.
  • Tests

    • Added test coverage for bot-authored message scenarios in retry receipt processing.

Walkthrough

Refined retry receipt suppression logic to align with WhatsApp Web by replacing the previous we_are_bot/sender_is_bot condition with a dedicated is_bot_authored_non_bot_chat() predicate. Added unit test validating the predicate across bot DM, bot group reply, and user DM scenarios.

Changes

Bot-authored message filtering in retry receipts

Layer / File(s) Summary
Bot-authored message suppression predicate and integration
src/retry.rs, wacore/src/types/message.rs
Replaced the dual we_are_bot and sender_is_bot suppression check in send_retry_receipt with info.source.is_bot_authored_non_bot_chat() predicate to match WhatsApp Web's abort conditions for bot-authored messages in non-bot chats. Added unit test is_bot_authored_non_bot_chat_matches_wa_web validating the new predicate behavior against three cases: bot DMs, bot replies in non-bot groups, and user DMs.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • oxidezap/whatsapp-rust#506: Introduced handle_decrypt_failure emission of retry receipts; this PR's filtering logic directly affects those receipts.

  • oxidezap/whatsapp-rust#659: Concurrent use of MessageSource::is_bot_authored_non_bot_chat() predicate in decrypt and ack self-fanout flows for the same suppression control.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main fix: correcting bot retry receipt suppression logic to match WhatsApp Web's behavior for bot DMs.
Description check ✅ Passed The description clearly explains the problem, the root cause, the fix using is_bot_authored_non_bot_chat(), test coverage, and confirms no breaking changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/bot-dm-retry-receipt-gate

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 Jun 4, 2026

Copy link
Copy Markdown

Benchmark Results

67 unchanged benchmark(s)
Benchmark Current Baseline Change
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message() 2,838 2,838 +0.0%
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message() 8,272 8,272 +0.0%
reporting_token_benchmark::key_derivation_group::bench_key_derivation 31,317 31,317 +0.0%
reporting_token_benchmark::token_calculation_group::bench_token_calculation 13,827 13,827 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple() 49,398 49,398 +0.0%
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended() 54,827 54,827 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message() 1,592 1,592 +0.0%
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message() 4,219 4,219 +0.0%
send_receive_benchmark::dm_send::bench_dm_send text:setup_dm_send() 112,836 113,083 -0.2%
send_receive_benchmark::dm_recv::bench_dm_recv text:setup_dm_recv() 1,656,119 1,656,232 -0.0%
send_receive_benchmark::group_send::bench_group_send group_10:setup_group_send_10() 650,272 650,159 +0.0%
send_receive_benchmark::group_send::bench_group_send group_50:setup_group_send_50() 873,788 873,769 +0.0%
send_receive_benchmark::group_send::bench_group_send group_256:setup_group_send_256() 2,081,678 2,081,600 +0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_10:setup_group_skdm_10() 747,404 746,454 +0.1%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_50:setup_group_skdm_50() 1,330,497 1,330,707 -0.0%
send_receive_benchmark::group_send_skdm::bench_group_send_skdm skdm_256:setup_group_skdm_256() 4,391,994 4,374,283 +0.4%
send_receive_benchmark::group_recv::bench_group_recv text:setup_group_recv() 515,822 519,812 -0.8%
binary_benchmark::marshal_group::bench_marshal_allocating 45,401 45,401 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_allocating 45,451 45,451 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_allocating 66,354 66,354 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer 43,512 43,512 +0.0%
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer 45,507 45,507 +0.0%
binary_benchmark::marshal_group::bench_marshal_long_string 4,930 4,930 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_long_string 4,961 4,961 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_long_string 6,732 6,732 +0.0%
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating 528,529 528,529 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating 528,150 528,150 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating 529,396 529,396 +0.0%
binary_benchmark::marshal_group::bench_marshal_many_children_allocating 5,417,786 5,417,786 +0.0%
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating 5,362,043 5,362,043 +0.0%
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating 13,276,336 13,276,336 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled() 1,850 1,850 +0.0%
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled() 29,217 29,217 +0.0%
binary_benchmark::unpack_group::bench_unpack_uncompressed 618 618 +0.0%
binary_benchmark::unpack_group::bench_unpack_compressed 672,890 672,890 +0.0%
binary_benchmark::attr_parser_group::bench_attr_parser attr_lookup:setup_attr_marshaled() 3,736 3,736 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled() 3,840 3,840 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled() 48,283 48,283 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled() 3,866 3,866 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled() 48,344 48,344 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled() 5,206 5,206 +0.0%
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled() 66,668 66,668 +0.0%
binary_benchmark::child_iteration_group::bench_get_children_by_tag 310,312 310,312 +0.0%
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled() 8,286 8,286 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u32 254 254 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u32 91 91 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_u64 292 292 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_u64 137 137 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_i64 317 317 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_i64 145 145 +0.0%
numeric_attr_benchmark::bench_group::bench_baseline_loop_100_u64 27,425 27,425 +0.0%
numeric_attr_benchmark::bench_group::bench_proposed_loop_100_u64 10,725 10,725 +0.0%
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users() 4,137,579 4,139,945 -0.1%
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session() 100,131 100,133 -0.0%
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message() 4,263,702 4,263,702 +0.0%
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session() 99,803 99,803 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender() 210,249 210,262 -0.0%
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution() 496,921 496,921 +0.0%
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message() 512,669 506,967 +1.1%
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data() 11,974,555 11,977,175 -0.0%
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message() 2,466,138 2,466,138 +0.0%
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message() 4,928,642 4,931,652 -0.1%
libsignal_benchmark::signature_group::bench_key_generation keygen 2,043,397 2,043,397 +0.0%
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions() 37,950 37,950 +0.0%
libsignal_benchmark::session_optimization_group::bench_out_of_order_decryption out_of_order:setup_out_of_order_messages() 3,616,076 3,616,076 +0.0%
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session() 223,150 223,150 +0.0%
libsignal_benchmark::session_optimization_group::bench_message_key_eviction eviction:setup_message_key_eviction() 9,980,959 9,980,959 +0.0%
No significant changes detected.

@jlucaso1
jlucaso1 merged commit 2341d81 into main Jun 4, 2026
11 checks passed
@jlucaso1
jlucaso1 deleted the fix/bot-dm-retry-receipt-gate branch June 4, 2026 10:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant