Skip to content

feat: handle stream error codes 401, 409, 429 - #353

Merged
jlucaso1 merged 2 commits into
mainfrom
feat/stream-error-codes
Mar 15, 2026
Merged

feat: handle stream error codes 401, 409, 429#353
jlucaso1 merged 2 commits into
mainfrom
feat/stream-error-codes

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Mar 14, 2026

Copy link
Copy Markdown
Collaborator

Fixes #342

Summary

  • 401 (unauthorized): disable auto-reconnect, dispatch LoggedOut — session is invalid
  • 409 (conflict): disable auto-reconnect, dispatch StreamReplaced — another client connected
  • 429 (rate limited): keep auto-reconnect, increase backoff by 5 fibonacci steps — server is throttling

Cross-referenced against WA Web WAWebHandleStreamError behavior.

Test plan

  • 4 unit tests verifying auto-reconnect flag and backoff for each code
  • cargo fmt --all clean
  • cargo clippy --all --tests clean

Summary by CodeRabbit

  • New Features
    • Automatic logout and disconnection on authentication failures
    • Automatic logout and disconnection when session is replaced
    • Rate limiting with intelligent backoff strategy for reconnection attempts

- 401 (unauthorized): disable auto-reconnect, dispatch LoggedOut
- 409 (conflict): disable auto-reconnect, dispatch StreamReplaced
- 429 (rate limited): keep auto-reconnect, increase backoff by 5 steps
@coderabbitai

coderabbitai Bot commented Mar 14, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@jlucaso1 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 13 minutes and 49 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b47d2e4a-2bee-4e6a-bb2e-ada95861ef3a

📥 Commits

Reviewing files that changed from the base of the PR and between 955ecfe and 21f4e25.

📒 Files selected for processing (1)
  • src/client.rs
📝 Walkthrough

Walkthrough

Added handling for three stream error codes (401 unauthorized, 409 conflict, 429 rate limited) in the client with specific recovery actions: 401 and 409 disable auto-reconnect and dispatch respective events, while 429 keeps auto-reconnect enabled and increases backoff delay. Includes tests for all three codes.

Changes

Cohort / File(s) Summary
Stream Error Handling
src/client.rs
Added three new error code paths: 401 logs out and disables auto-reconnect, 409 dispatches StreamReplaced event with auto-reconnect disabled, 429 increases backoff counter by 5 while keeping auto-reconnect enabled. Includes test helper function and comprehensive test cases for all three error codes.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Poem

🐰 Stream errors three, now handled with care,
401 says "logout," 409's a pair,
429 whispers "slow down, wait a beat,"
Backoff adjusts and clients reconnect neat! 🔌✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding explicit handling for three stream error codes (401, 409, 429) in the client.
Linked Issues check ✅ Passed The PR implements all coding requirements from #342: handles 401 (disables auto-reconnect, dispatches LoggedOut), 409 (disables auto-reconnect, dispatches StreamReplaced), and 429 (keeps auto-reconnect, increases backoff). Tests verify these behaviors.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing stream error handling for codes 401, 409, and 429, with supporting test helpers and test cases. No unrelated modifications are present.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/stream-error-codes
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@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.

🧹 Nitpick comments (2)
src/client.rs (2)

4639-4654: Consolidate duplicated test-client helpers.

create_test_client() duplicates create_offline_sync_test_client() setup. Reusing one helper will keep test setup changes centralized.

♻️ Suggested simplification
-    async fn create_test_client() -> Arc<Client> {
-        let backend = crate::test_utils::create_test_backend().await;
-        let pm = Arc::new(
-            PersistenceManager::new(backend)
-                .await
-                .expect("persistence manager should initialize"),
-        );
-        let (client, _rx) = Client::new(
-            pm,
-            Arc::new(crate::transport::mock::MockTransportFactory::new()),
-            Arc::new(MockHttpClient),
-            None,
-        )
-        .await;
-        client
-    }
+    // Reuse existing create_offline_sync_test_client()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client.rs` around lines 4639 - 4654, create_test_client() duplicates the
setup in create_offline_sync_test_client(); replace the duplicate by reusing the
existing helper to centralize test setup. Locate the create_test_client function
and change it to call create_offline_sync_test_client() (or extract the shared
setup into a new helper used by both) instead of repeating the
PersistenceManager::new(...) and Client::new(...) logic; ensure the returned
type remains Arc<Client> and any required mocks (MockTransportFactory,
MockHttpClient) are provided by the reused helper or moved into the shared
helper used by both functions.

4656-4704: Strengthen stream-error tests for event semantics and exact backoff delta.

These tests cover reconnect flags well, but they currently don’t assert emitted events for 401/409, and 429 uses a >= check that can hide regressions.

✅ Minimal assertion hardening for 429
-        assert!(
-            after >= before + 5,
-            "429 should increase backoff: before={before}, after={after}"
-        );
+        assert_eq!(
+            after,
+            before + 5,
+            "429 should increase backoff by exactly 5: before={before}, after={after}"
+        );

Consider also adding event-bus assertions:

  • 401 dispatches Event::LoggedOut
  • 409 dispatches Event::StreamReplaced
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client.rs` around lines 4656 - 4704, The tests calling
client.handle_stream_error (test_stream_error_401_disables_reconnect,
test_stream_error_409_disables_reconnect,
test_stream_error_429_keeps_reconnect_with_backoff) should also assert the
expected events and tighten the 429 backoff check: after invoking
client.handle_stream_error(&node) add an assertion that the client's event bus
emits Event::LoggedOut for code "401" and Event::StreamReplaced for code "409"
(use the test client's event receiver/queue to pop the emitted event), and
change the 429 backoff assertion to require an exact increment of +5 to
client.auto_reconnect_errors (i.e., assert after == before + 5) while keeping
the enable_auto_reconnect checks using
client.enable_auto_reconnect.load(Ordering::Relaxed).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/client.rs`:
- Around line 4639-4654: create_test_client() duplicates the setup in
create_offline_sync_test_client(); replace the duplicate by reusing the existing
helper to centralize test setup. Locate the create_test_client function and
change it to call create_offline_sync_test_client() (or extract the shared setup
into a new helper used by both) instead of repeating the
PersistenceManager::new(...) and Client::new(...) logic; ensure the returned
type remains Arc<Client> and any required mocks (MockTransportFactory,
MockHttpClient) are provided by the reused helper or moved into the shared
helper used by both functions.
- Around line 4656-4704: The tests calling client.handle_stream_error
(test_stream_error_401_disables_reconnect,
test_stream_error_409_disables_reconnect,
test_stream_error_429_keeps_reconnect_with_backoff) should also assert the
expected events and tighten the 429 backoff check: after invoking
client.handle_stream_error(&node) add an assertion that the client's event bus
emits Event::LoggedOut for code "401" and Event::StreamReplaced for code "409"
(use the test client's event receiver/queue to pop the emitted event), and
change the 429 backoff assertion to require an exact increment of +5 to
client.auto_reconnect_errors (i.e., assert after == before + 5) while keeping
the enable_auto_reconnect checks using
client.enable_auto_reconnect.load(Ordering::Relaxed).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7337e192-7a47-4546-a868-eb83533cc7b3

📥 Commits

Reviewing files that changed from the base of the PR and between 555a1d4 and 955ecfe.

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

@github-actions

github-actions Bot commented Mar 14, 2026

Copy link
Copy Markdown

🐰 Bencher Report

Branchfeat/stream-error-codes
Testbedubuntu-latest
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,323.00
(-7.62%)Baseline: 6,844.22
7,186.43
(87.99%)
binary_benchmark::child_iteration_group::bench_get_children_by_tag📈 view plot
🚷 view threshold
850,843.00
(+0.01%)Baseline: 850,785.24
893,324.50
(95.24%)
binary_benchmark::jid_optimization_group::bench_jid_to_owned_access jid_access:setup_jid_heavy_marshaled()📈 view plot
🚷 view threshold
23,058.00
(-1.18%)Baseline: 23,332.77
24,499.41
(94.12%)
binary_benchmark::marshal_group::bench_marshal_allocating📈 view plot
🚷 view threshold
119,261.00
(-8.19%)Baseline: 129,894.26
136,388.97
(87.44%)
binary_benchmark::marshal_group::bench_marshal_auto_allocating📈 view plot
🚷 view threshold
119,289.00
(+0.04%)Baseline: 119,242.51
125,204.63
(95.28%)
binary_benchmark::marshal_group::bench_marshal_auto_huge_bytes_allocating📈 view plot
🚷 view threshold
534,036.00
(+0.00%)Baseline: 534,032.67
560,734.31
(95.24%)
binary_benchmark::marshal_group::bench_marshal_auto_long_string📈 view plot
🚷 view threshold
17,363.00
(+0.05%)Baseline: 17,354.73
18,222.46
(95.28%)
binary_benchmark::marshal_group::bench_marshal_auto_many_children_allocating📈 view plot
🚷 view threshold
17,139,029.00
(+0.02%)Baseline: 17,135,839.49
17,992,631.47
(95.26%)
binary_benchmark::marshal_group::bench_marshal_exact_allocating📈 view plot
🚷 view threshold
176,762.00
(+0.03%)Baseline: 176,715.51
185,551.28
(95.26%)
binary_benchmark::marshal_group::bench_marshal_exact_huge_bytes_allocating📈 view plot
🚷 view threshold
535,449.00
(+0.00%)Baseline: 535,445.67
562,217.96
(95.24%)
binary_benchmark::marshal_group::bench_marshal_exact_long_string📈 view plot
🚷 view threshold
19,417.00
(+0.04%)Baseline: 19,408.73
20,379.16
(95.28%)
binary_benchmark::marshal_group::bench_marshal_exact_many_children_allocating📈 view plot
🚷 view threshold
42,776,646.00
(+0.01%)Baseline: 42,770,296.78
44,908,811.62
(95.25%)
binary_benchmark::marshal_group::bench_marshal_huge_bytes_allocating📈 view plot
🚷 view threshold
534,475.00
(+0.00%)Baseline: 534,471.67
561,195.26
(95.24%)
binary_benchmark::marshal_group::bench_marshal_long_string📈 view plot
🚷 view threshold
17,336.00
(-4.55%)Baseline: 18,161.86
19,069.96
(90.91%)
binary_benchmark::marshal_group::bench_marshal_many_children_allocating📈 view plot
🚷 view threshold
17,139,912.00
(+0.02%)Baseline: 17,136,670.67
17,993,504.21
(95.26%)
binary_benchmark::marshal_group::bench_marshal_reusing_buffer📈 view plot
🚷 view threshold
129,162.00
(-3.38%)Baseline: 133,686.75
140,371.09
(92.01%)
binary_benchmark::marshal_group::bench_marshal_reusing_buffer_vec_writer📈 view plot
🚷 view threshold
119,361.00
(+0.04%)Baseline: 119,314.51
125,280.23
(95.28%)
binary_benchmark::roundtrip_group::bench_roundtrip large:setup_large_marshaled()📈 view plot
🚷 view threshold
94,389.00
(-5.89%)Baseline: 100,297.38
105,312.25
(89.63%)
binary_benchmark::roundtrip_group::bench_roundtrip small:setup_small_marshaled()📈 view plot
🚷 view threshold
7,378.00
(-6.70%)Baseline: 7,908.12
8,303.52
(88.85%)
binary_benchmark::roundtrip_group::bench_roundtrip_auto large:setup_large_marshaled()📈 view plot
🚷 view threshold
94,420.00
(+0.46%)Baseline: 93,990.09
98,689.60
(95.67%)
binary_benchmark::roundtrip_group::bench_roundtrip_auto small:setup_small_marshaled()📈 view plot
🚷 view threshold
7,401.00
(+1.12%)Baseline: 7,318.91
7,684.85
(96.31%)
binary_benchmark::roundtrip_group::bench_roundtrip_exact large:setup_large_marshaled()📈 view plot
🚷 view threshold
110,205.00
(+0.39%)Baseline: 109,775.09
115,263.85
(95.61%)
binary_benchmark::roundtrip_group::bench_roundtrip_exact small:setup_small_marshaled()📈 view plot
🚷 view threshold
8,913.00
(+0.93%)Baseline: 8,830.91
9,272.45
(96.12%)
binary_benchmark::unmarshal_group::bench_unmarshal large:setup_large_marshaled()📈 view plot
🚷 view threshold
45,476.00
(-4.81%)Baseline: 47,773.92
50,162.62
(90.66%)
binary_benchmark::unmarshal_group::bench_unmarshal small:setup_small_marshaled()📈 view plot
🚷 view threshold
2,717.00
(-7.80%)Baseline: 2,946.75
3,094.09
(87.81%)
binary_benchmark::unpack_group::bench_unpack_compressed📈 view plot
🚷 view threshold
556,092.00
(+4.63%)Baseline: 531,494.75
558,069.48
(99.65%)
binary_benchmark::unpack_group::bench_unpack_uncompressed📈 view plot
🚷 view threshold
771.00
(-0.55%)Baseline: 775.28
814.04
(94.71%)
libsignal_benchmark::conversation_group::bench_full_dm_conversation full:setup_conversation_data()📈 view plot
🚷 view threshold
27,719,466.00
(-0.03%)Baseline: 27,728,961.88
29,115,409.97
(95.21%)
libsignal_benchmark::dm_group::bench_dm_decrypt_first_message decrypt_prekey:setup_dm_with_first_message()📈 view plot
🚷 view threshold
5,540,322.00
(-0.23%)Baseline: 5,552,909.13
5,830,554.59
(95.02%)
libsignal_benchmark::dm_group::bench_dm_encrypt_first_message first_msg:setup_dm_session()📈 view plot
🚷 view threshold
178,094.00
(+0.04%)Baseline: 178,027.05
186,928.40
(95.27%)
libsignal_benchmark::dm_group::bench_dm_encrypt_subsequent_message subsequent:setup_established_dm_session()📈 view plot
🚷 view threshold
178,905.00
(+0.04%)Baseline: 178,839.04
187,781.00
(95.27%)
libsignal_benchmark::dm_group::bench_dm_session_establishment setup:setup_dm_users()📈 view plot
🚷 view threshold
17,266,692.00
(-0.11%)Baseline: 17,285,809.89
18,150,100.38
(95.13%)
libsignal_benchmark::group_messaging_group::bench_group_create_distribution_message create:setup_group_sender()📈 view plot
🚷 view threshold
295,820.00
(-0.01%)Baseline: 295,836.31
310,628.13
(95.23%)
libsignal_benchmark::group_messaging_group::bench_group_decrypt_message decrypt:setup_group_with_encrypted_message()📈 view plot
🚷 view threshold
12,584,427.00
(-0.12%)Baseline: 12,599,900.88
13,229,895.92
(95.12%)
libsignal_benchmark::group_messaging_group::bench_group_encrypt_message encrypt:setup_group_with_distribution()📈 view plot
🚷 view threshold
715,609.00
(-0.02%)Baseline: 715,740.34
751,527.36
(95.22%)
libsignal_benchmark::session_optimization_group::bench_decrypt_with_previous_session previous_session:setup_with_archived_sessions()📈 view plot
🚷 view threshold
41,823.00
(+0.04%)Baseline: 41,807.49
43,897.86
(95.27%)
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,659.01
16,339,741.96
(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,504,859.00
(-0.15%)Baseline: 5,513,375.59
5,789,044.37
(95.09%)
libsignal_benchmark::session_optimization_group::bench_promote_matching_session promote:setup_promote_matching_session()📈 view plot
🚷 view threshold
956,774.00
(-0.30%)Baseline: 959,694.34
1,007,679.06
(94.95%)
libsignal_benchmark::signature_group::bench_key_generation keygen📈 view plot
🚷 view threshold
2,822,723.00
(-0.01%)Baseline: 2,822,934.11
2,964,080.82
(95.23%)
libsignal_benchmark::signature_group::bench_signature_creation sign:setup_keypair_with_message()📈 view plot
🚷 view threshold
3,444,364.00
(-1.52%)Baseline: 3,497,440.29
3,672,312.30
(93.79%)
libsignal_benchmark::signature_group::bench_signature_verification verify:setup_keypair_with_message()📈 view plot
🚷 view threshold
125,361,055.00
(-0.09%)Baseline: 125,468,800.55
131,742,240.57
(95.16%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction extended:setup_extended_message()📈 view plot
🚷 view threshold
11,812.00
(+0.15%)Baseline: 11,794.36
12,384.07
(95.38%)
reporting_token_benchmark::content_extraction_group::bench_content_extraction simple:setup_simple_message()📈 view plot
🚷 view threshold
3,825.00
(+0.05%)Baseline: 3,822.92
4,014.06
(95.29%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation extended:setup_full_gen_extended()📈 view plot
🚷 view threshold
87,972.00
(+0.00%)Baseline: 87,971.06
92,369.61
(95.24%)
reporting_token_benchmark::full_generation_group::bench_full_token_generation simple:setup_full_gen_simple()📈 view plot
🚷 view threshold
80,008.00
(-0.02%)Baseline: 80,022.62
84,023.75
(95.22%)
reporting_token_benchmark::key_derivation_group::bench_key_derivation📈 view plot
🚷 view threshold
51,011.00
(-0.02%)Baseline: 51,023.36
53,574.53
(95.22%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding extended:setup_extended_message()📈 view plot
🚷 view threshold
5,762.00
(+0.33%)Baseline: 5,742.95
6,030.10
(95.55%)
reporting_token_benchmark::message_encoding_group::bench_message_encoding simple:setup_simple_message()📈 view plot
🚷 view threshold
2,122.00
(+0.23%)Baseline: 2,117.05
2,222.90
(95.46%)
reporting_token_benchmark::token_calculation_group::bench_token_calculation📈 view plot
🚷 view threshold
21,920.00
(+0.02%)Baseline: 21,914.58
23,010.31
(95.26%)
🐰 View full continuous benchmarking report in Bencher

@jlucaso1
jlucaso1 merged commit 09f9a6b into main Mar 15, 2026
8 checks passed
@jlucaso1
jlucaso1 deleted the feat/stream-error-codes branch March 15, 2026 00:16
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.

feat: handle stream error codes 401, 409, 429

1 participant