Skip to content

refactor(error)!: replace anyhow in public APIs with per-domain typed errors - #893

Merged
jlucaso1 merged 8 commits into
mainfrom
refactor/per-domain-typed-errors
Jun 18, 2026
Merged

refactor(error)!: replace anyhow in public APIs with per-domain typed errors#893
jlucaso1 merged 8 commits into
mainfrom
refactor/per-domain-typed-errors

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator

What

The public API leaked ~5 inconsistent error families (anyhow::Error, MexError, PresenceError, IqError, ClientError) with no rule, often mixed within a single module (e.g. groups.rs returned anyhow from leave but MexError from set_limit_sharing; presence.rs mixed PresenceError and anyhow). This replaces them with one consistent typed error per feature domain, all built on a shared ClientError base. anyhow no longer appears in any migrated public signature.

Design

  • Shared base ClientError — the DRY transport/connection foundation (NotConnected, NotLoggedIn, Socket, EncryptSend, now Iq(#[from] IqError) + a transparent anyhow catch-all). Each domain error embeds it via #[from]. It is not an umbrella: the per-domain types remain the public return types.
  • Per-domain errorsthiserror, #[non_exhaustive], #[from] for chaining ClientError/IqError/MexError so internal ? keeps working. Common actionable cases (NotLoggedIn, IQ failures, Server { code, text }, invalid-request validation) are typed explicitly; a last-resort transparent Internal(#[from] anyhow::Error) keeps the migration tractable where no typed case exists yet.
  • Re-exports — every public domain error plus ClientError and IqError are re-exported at the crate root, and ClientError / IqError / SendError are added to the prelude.

Migration (public return types changed)

  • Send path (Client): send_message, send_text, forward_message, send_message_with_options, revoke_message, edit_message, edit_message_encrypted, keep_message, pin_message/unpin_message, set_chat_disappearing_timer, send_reaction -> SendError.
  • MessageContext (bot.rs): send_message, reply, reply_quoting, edit_message, revoke_message, react -> SendError.
  • Feature modules (one error each): groups -> GroupError; contacts -> ContactError; status -> SendError; blocking -> BlockingError; chatstate -> ChatStateError; presence -> PresenceError (extended with Client(#[from] ClientError)); community -> CommunityError; newsletter -> NewsletterError; profile -> ProfileError; polls -> PollError; events/comments -> SendError; signal -> SignalError; tc_token -> TcTokenError; media reupload -> MediaReuploadError; chat actions + labels -> shared AppStateError. (spam_report already returned IqError.)

Breaking change

Every listed method's Err variant changed. Callers using ? into an anyhow context are unaffected (each new error implements std::error::Error); callers that matched on anyhow::Error must now match the typed variant. IqError::ClientState is now boxed to break the ClientError <-> IqError type cycle.

Verification

cargo fmt --all, cargo build --all, cargo build --all --examples, cargo clippy --all --tests (no warnings), and cargo test -p wacore -p whatsapp-rust all pass locally (e2e not run — needs the mock server).

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

This PR was authored by the user configured for CodeRabbit reviews. CodeRabbit does not review PRs authored by this user. It's recommended to use a dedicated user account to post CodeRabbit review feedback.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1eb15d8d-7e87-4b95-bf45-49ac82fdf293

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This is a comprehensive API overhaul. We're replacing vague anyhow::Error with a proper typed error hierarchy. That means SendError becomes the single source of truth for send-path failures—with downcast logic that recovers logged-out state from bubbled anyhow::Error, so callers don't have to dig through chains. ClientError gains Iq and Internal variants as a shared transport layer. Every feature module—Groups, Newsletter, Blocking, ChatActions, Polls, Signal, MediaReupload, TcToken, Community, Contacts, Presence, Profile, Events, Comments, Reaction, Status—now owns its own typed error domain with explicit validation. No more generic "something failed." No more unwrapping anyhow chains hoping for the right type. No more surprises at runtime. This is how you build a library users can actually trust.

Changes

Typed Error Hierarchy Migration

Layer / File(s) Summary
Core error foundation: SendError, ClientError, IqError
src/send.rs, src/client.rs, src/request.rs, src/lib.rs
SendError enum with Client, NotLoggedIn, Iq, InvalidRequest(String), and Internal variants plus from_anyhow downcast helper that intelligently routes bubbled anyhow::Error back into matchable typed variants. Logged-out sends surface as SendError::NotLoggedIn, not buried in Internal. ClientError gains Iq(#[from] IqError) and Internal(#[from] anyhow::Error) variants to centralize transport-layer and protocol failures. IqError::ClientState now boxes ClientError to prevent type cycle. send_and_wait_iq error mapping expanded to convert any unhandled ClientError variant into ClientState. Crate root re-exports ClientError, IqError, and SendError. Prelude includes all three. This is the architecture that makes real error handling possible. You want it right from the start.
Send path implementation returns SendError
src/send.rs
send_message, send_text, forward_message, send_message_with_options all return SendError. send_status_message validates recipients upfront: empty list rejected as InvalidRequest, missing device PN/LID produces NotLoggedIn or InvalidRequest, non-user recipients rejected as InvalidRequest with format message. revoke_message maps require_pn failures through from_anyhow. Admin-only revoke enforced as InvalidRequest with message. keep_message, pin_message, unpin_message all return SendError. send_message_impl rejects misrouted newsletter JIDs and invalid status@broadcast reaction participants with InvalidRequest. Regression test validates logged-out DM returns SendError::NotLoggedIn. Every failure is explicit. Every success path is clear.
Client edit paths and set_chat_disappearing_timer return SendError
src/client/messaging.rs, src/client/iq_ops.rs
edit_message and edit_message_encrypted public signatures return SendError. edit_message_inner maps missing PN to NotLoggedIn, group JID errors through from_anyhow. edit_message_encrypted_inner replaces anyhow::ensure! with explicit early returns: newsletter/channel targets rejected as InvalidRequest, message_secret length validated at exactly 32 bytes (actual length included in error). Missing PN maps to NotLoggedIn. set_chat_disappearing_timer returns SendError, validates 1:1-only with structured InvalidRequest message instructing to use Groups::set_ephemeral for non-1:1 chats. This is validation with guidance. Not "invalid" with no context.
MessageContext send/reply/edit/revoke/react return SendError
src/bot.rs
All MessageContext public methods—send_message, reply, reply_quoting, edit_message, revoke_message, react—now return SendError instead of anyhow::Error. react signature reformatted to multi-line for clarity. Every entry point from the bot context is typed. Callers know exactly what they're handling.
Feature send APIs return SendError: reaction, status, events, comments
src/features/reaction.rs, src/features/status.rs, src/features/events.rs, src/features/comments.rs
Client::send_reaction returns SendError; encrypted path rejects missing target key id as InvalidRequest("target message key missing id"), missing reactor as NotLoggedIn. All Status send/revoke methods return SendError. Events::create validates empty event name as InvalidRequest("event name must not be empty"). Events::respond validates message_secret exactly 32 bytes, returns InvalidRequest with actual length on mismatch; missing login state returns NotLoggedIn. Comments::send_text and send_message return SendError with proper validation. Per-comment secret changed from heap Vec<u8> to fixed [u8; 32] array. Direct reference passing to envelope and persistence. Cleaner. Faster. Correct.
GroupError for all group operations with structured validation
src/features/groups.rs
GroupError with Iq, Mex, InvalidRequest(String), Internal variants. All Groups public methods return GroupError. query_info converts "not-modified but nothing cached" to InvalidRequest. create_group maps missing LID↔PN mapping to InvalidRequest. join_with_invite_code and join_with_invite_v4 validate non-empty invite code and V4 expiration, returning InvalidRequest with specific messages ("V4 invite expired: expiration={ts}, now={ts}"). batch_get_info and get_profile_pictures enforce batch size limits. update_member_label requires group JID, returns InvalidRequest if not. MEX-based setters unified under GroupError. Everything is explicitly validated with meaning, not "something failed".
AppStateError for mutations and ProfileError for profile operations
src/features/chat_actions.rs, src/features/labels.rs, src/features/profile.rs
AppStateError with InvalidRequest(String) and Internal variants. All ChatActions public methods return AppStateError with explicit validation: save_contact rejects non-PN JIDs, mute_chat_until validates timestamp. send_app_state_mutation maps missing sync key to InvalidRequest, retrieval failures to Internal. Labels::create_label and delete_label validate non-empty IDs and names. ProfileError with Iq, Client, InvalidArgument, Internal variants. All profile methods return ProfileError. set_push_name rejects empty name as InvalidArgument. Validation is upfront. Errors are specific. No buried surprises.
BlockingError, ContactError, PresenceError, ChatStateError
src/features/blocking.rs, src/features/contacts.rs, src/features/presence.rs, src/features/chatstate.rs
BlockingError with Iq, InvalidJid, Internal variants. resolve_lid_pn validates PN/LID type, maps invalid/missing to InvalidJid. ContactError with Iq and InvalidJid. ensure_is_on_whatsapp_jids_supported validates types, rejects unsupported as InvalidJid. PresenceError gains Client(#[from] ClientError) variant. Presence methods use direct ? propagation so send_node failures surface as Client instead of wrapped. ChatStateError wraps ClientError. All methods return their typed errors. Direct propagation. No wrapping. What fails is visible.
CommunityError and NewsletterError with validated response data
src/features/community.rs, src/features/newsletter.rs
CommunityError consolidates Iq, Mex, Group, InvalidRequest(String), Internal. MEX subgroup fetchers convert missing response.data to InvalidRequest. NewsletterError with Mex, Iq, Client, InvalidRequest(String), Internal plus downcast helper. All methods and parsers return NewsletterError. MEX handlers convert null/missing payloads to InvalidRequest with field name. IQ parsers map missing required fields to InvalidRequest. No silent failures on malformed server responses.
PollError, SignalError, MediaReuploadError, TcTokenError
src/features/polls.rs, src/features/signal.rs, src/features/media_reupload.rs, src/features/tctoken.rs
PollError validates poll options (2–10), selectable count, no duplicates, quiz correct_index in-bounds. SignalError for protocol operations, unsupported encryption types, internal failures. MediaReuploadError validates newsletter messages, maps timeout and cancellation appropriately. TcTokenError wraps both IqError and StoreError. All methods return their typed errors. Persistence failures visible. Crypto failures typed. No mysteries.
Feature module re-exports and addon parent resolution
src/features/mod.rs, src/message/msg_secret.rs, tests/bench-integration/src/main.rs
features/mod.rs expands pub use lists to surface all new error types alongside feature types. Error types are now public API, not implementation details. resolve_outgoing_addon_parent now returns SendError with explicit validation for target_key data and missing messageSecret with descriptive messages. Benchmark closure wraps send_message result in Ok(...) for proper alignment. The interface is complete and consistent.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • oxidezap/whatsapp-rust#90: Both PRs modify the MessageContext API surface in src/bot.rs—specifically send_message/edit_message signatures—so error-type changes here directly update the bot's public contract.
  • oxidezap/whatsapp-rust#268: Both PRs touch group management surface in src/features/groups.rs—this PR introduces GroupError for all those same APIs.
  • oxidezap/whatsapp-rust#304: Both PRs update group settings APIs in src/features/groups.rs—retrieved PR extends those methods while this PR changes their error surface to typed GroupError.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor(error)!: replace anyhow in public APIs with per-domain typed errors' directly and clearly describes the main change—replacing inconsistent error types with per-domain typed errors.
Description check ✅ Passed The description is thorough and related to the changeset, explaining the motivation, design approach, migration scope, and verification steps for the error refactoring.
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.


Comment @coderabbitai help to get the list of available commands and usage tips.

@jlucaso1

Copy link
Copy Markdown
Collaborator Author

@claude review

@cubic-dev-ai cubic-dev-ai 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.

2 issues found across 28 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/features/chat_actions.rs
Comment thread src/features/newsletter.rs
@claude

claude Bot commented Jun 18, 2026

Copy link
Copy Markdown

Claude finished @jlucaso1's task in 3m 39s —— View job


Review

Solid migration overall — the ClientError base + per-domain typed errors with #[from] chaining pattern is clean, #[non_exhaustive] is applied consistently, and the IqError cycle is resolved correctly via Box. The boxing rationale (src/request.rs:36-39) is documented well, and the prelude/re-exports give consumers a single coherent surface. The presence test at src/features/presence.rs:349 showing how to match on PresenceError::Client(_) is also a nice demonstration.

The cubic findings are both legitimate and they point at the same systemic issue, which is the most important thing to address before merging.


Findings

🔴 P2 — chat_actions.rs:25 Validation errors collapse into Internal (cubic finding 1, confirmed)

AppStateError::Internal has #[from] anyhow::Error, and three internal helpers still return anyhow::Result using anyhow::bail!:

  • message_key_owned (src/features/chat_actions.rs:316-326) raises "participant_jid is required for group messages not sent by us" — exactly the case the doc comment at src/features/chat_actions.rs:26-28 claims maps to InvalidRequest ("missing group participant"). It currently lands in Internal.
  • build_action_index (src/features/chat_actions.rs:343-365) raises an index-arg-count mismatch — also a validation failure that should be InvalidRequest.
  • send_app_state_mutation (src/features/chat_actions.rs:759) wraps get_latest_sync_key_id's store error with .map_err(|e| anyhow::anyhow!(e))? so a store failure also becomes Internal (here arguably correct, but inconsistent — the very next call uses AppStateError::Internal(e.into()) explicitly).

Public callers affected: delete_message_for_me, send_star_mutation/unstar_message/star_message, every send_app_state_action consumer (clear_chat, contact, labels). Each falsely classifies a bad-input failure as Internal.

Fix: change the two helpers to return Result<_, AppStateError> directly with AppStateError::InvalidRequest(...), and convert the store-key path to a typed variant. Then drop the From<anyhow::Error> (or at least stop using it as a validation conduit). Fix this →

🔴 P2 — newsletter.rs:435 send_reaction erases ClientError typing (cubic finding 2, confirmed)

Client::send_server_reaction (src/client/messaging.rs:206-211) returns Result<(), anyhow::Error> even though its body only does self.send_node(stanza).await? and send_node itself returns Result<(), ClientError> (src/client/messaging.rs:25). The typed ClientError gets boxed into anyhow and then lands in NewsletterError::Internal via #[from] anyhow::Error, instead of NewsletterError::Client(ClientError).

The same boxing happens through is_community_announce_group (src/features/reaction.rs:60), which returns anyhow::Error and is called from send_reaction: a typed GroupError::Iq(IqError::ServerError { code: 401, .. }) from query_info() is now indistinguishable from any other internal failure to the caller.

Fix: narrow send_server_reaction to Result<(), ClientError> (only send_node is called); narrow is_community_announce_group to Result<bool, GroupError> (drop the now-unused anyhow import where applicable). Fix this →

⚠️ Systemic — #[from] anyhow::Error as a transparent escape hatch is a typing trapdoor

The two findings above are instances of the same pattern: every domain error embeds #[non_exhaustive] + Internal(#[from] anyhow::Error) to keep ? plumbing tractable during migration. But because From<anyhow::Error> is a valid ? target for any typed error funneled through anyhow::Result, any internal helper still returning anyhow::Result silently downgrades typed errors at the call site.

grep -nE '-> anyhow::Result|-> Result<.*,\s*anyhow::Error>' over src/ shows the trapdoors still in scope:

  • src/usync.rs:13, 231 (get_user_devices, sync_own_device_list)
  • src/client/device_registry.rs:95, 195 (group device resolution)
  • src/client/messaging.rs:211, 397 (the one above + build_edit_message)
  • src/client/accessors.rs:170 (LID resolution)
  • src/client/app_state.rs:44, 64, 130 (app state retry/fetch)
  • src/features/reaction.rs:60 (the one above)
  • src/client/lid_pn.rs:557 (get_lid_pn_entry)

Every call into these from a ? in a typed-error function lands in Internal. This isn't a release blocker for this PR (the design is explicit about the tradeoff), but the docstrings on InvalidRequest/Client/etc. are then aspirational — callers can't actually rely on matching them.

Two paths forward worth considering:

  1. Tighten the inner helpers (the more impactful, multi-PR option): convert the internal anyhow::Result boundary helpers to typed errors, prioritising those called from the new public APIs.
  2. Drop #[from] anyhow::Error, keep Internal(anyhow::Error) as a manual .map_err(...) step — the conversion becomes explicit and a typed error passed through ? no longer needs From<anyhow::Error> to be in scope at all. This costs one .map_err() per call site but kills the silent-downgrade trapdoor in one stroke.

🟡 Low — minor polish

  • src/features/presence.rs:20 calls the catch-all Other(#[from] anyhow::Error) while every other migrated domain calls it Internal. Worth aligning for consistency.
  • src/request.rs:84 has a wacore::request::IqError::_ fallback arm that silently maps any new upstream variant to InternalChannelClosed. With #[non_exhaustive] on both sides, a log/warn on the unhandled variant would surface registry drift earlier. (Existing behaviour, not introduced by this PR.)
  • src/features/groups.rs:50-53 docs Internal as "catch-all for LID/PN resolution, the protocol-message send path behind update_member_label, cache plumbing" — fine, but those subsystems are exactly the ones returning anyhow::Result today, so any matching guidance for GroupError::Iq vs Internal is currently best-effort.

Verdict

The shape of the design is right and the public surface improvement is real. The two cubic findings are valid and worth fixing in this PR — they're the easiest wins of the systemic issue and removing them makes the documented variant taxonomy actually true for those entry points. The broader trapdoor is a follow-up.

  • Read the new ClientError foundation and key error types
  • Verify the chat_actions.rs concern (validation -> Internal) — confirmed at lines 316, 343, 759
  • Verify the newsletter.rs send_reaction concern — confirmed at messaging.rs:211
  • Spot-check other migrated domains for similar typing-erasure patterns — pervasive (8 helpers listed)
  • Post review feedback
    — branch: refactor/per-domain-typed-errors

@github-actions

github-actions Bot commented Jun 18, 2026

Copy link
Copy Markdown

📦 Binary size report

Metric main PR Δ
bin size (stripped) 10.56 MiB 10.59 MiB +23.69 KiB (+0.22%) 🔺
bin .text 8.68 MiB 8.70 MiB +20.94 KiB (+0.24%) 🔺
bin allocated (text+data+bss) 10.56 MiB 10.59 MiB +24.74 KiB (+0.23%) 🔺
llvm-lines wacore 639,574 639,574 0
llvm-lines wacore copies 17,666 17,666 0
llvm-lines whatsapp-rust lib 650,732 654,867 +4,135 (+0.64%) 🔺
llvm-lines whatsapp-rust lib copies 20,022 20,155 +133 (+0.66%) 🔺
deps crates (Cargo.lock) 341 341 0
.text per crate
Crate main PR Δ
.text whatsapp_rust 1.43 MiB 1.44 MiB +12.33 KiB (+0.84%) 🔺
.text wacore 518.06 KiB 518.02 KiB -44 B (-0.01%) 🔽
.text wacore_binary 157.70 KiB 156.73 KiB -987 B (-0.61%) 🔽
.text wacore_libsignal 166.67 KiB 166.63 KiB -44 B (-0.03%) 🔽
.text wacore_appstate 36.61 KiB 36.61 KiB 0
.text wacore_noise 29.06 KiB 27.71 KiB -1.35 KiB (-4.65%) 🎉
.text waproto 876.21 KiB 876.21 KiB 0
.text whatsapp_rust_sqlite_storage 207.48 KiB 207.48 KiB 0
.text whatsapp_rust_tokio_transport 32.49 KiB 32.49 KiB 0
.text whatsapp_rust_ureq_http_client 5.93 KiB 5.93 KiB 0
.text std 1.13 MiB 1.13 MiB +5.80 KiB (+0.50%) 🔺
.text other deps 4.07 MiB 4.07 MiB +4.99 KiB (+0.12%) 🔺
Top movers (cargo-bloat attribution)
Crate main PR Δ
whatsapp_rust 1.43 MiB 1.44 MiB +12.33 KiB (+0.84%)
std 1.13 MiB 1.13 MiB +5.80 KiB (+0.50%)
demo 53.80 KiB 57.33 KiB +3.53 KiB (+6.57%)
wacore_noise 29.06 KiB 27.71 KiB -1.35 KiB (-4.65%)

Baseline: 2d8fa7f57 (latest main run) · Head: 667a468bc · Graphs

@jlucaso1

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@jlucaso1
jlucaso1 force-pushed the refactor/per-domain-typed-errors branch from 7005bf3 to 3e0048e Compare June 18, 2026 02:33

@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: 7005bf3da2

ℹ️ 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/send.rs
@codspeed-hq

codspeed-hq Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Merging this PR will not alter performance

✅ 176 untouched benchmarks


Comparing refactor/per-domain-typed-errors (83bae60) with main (2d8fa7f)

Open in CodSpeed

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 1 file (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread src/send.rs Outdated

@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: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
src/features/chat_actions.rs (1)

754-766: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider simplifying error conversion.

The explicit map_err(|e| AppStateError::Internal(e.into())) on line 766 is a bit verbose. Since AppStateError::Internal has #[from] anyhow::Error, you could potentially use .map_err(|e| anyhow::anyhow!(e))? to let the #[from] conversion kick in. But honestly, the explicit mapping is fine - it's clear about intent. Just flagging for awareness.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/chat_actions.rs` around lines 754 - 766, The error conversion in
the get_app_state_key call on line 766 can be simplified by leveraging the
#[from] attribute that already exists on AppStateError::Internal. Instead of
using map_err(|e| AppStateError::Internal(e.into())), you can use the ? operator
with a simpler map_err that converts the error type using the From trait, or
simply remove the explicit conversion if AppStateError::Internal already
implements From for anyhow::Error. This will make the code more concise while
maintaining the same error handling behavior.
src/features/events.rs (1)

78-104: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate RSVP message_secret before encryption.

message_secret is caller input. If it is not the expected 32 bytes, the encryption helper’s error now becomes SendError::Internal, so callers can’t match it as a bad request. Validate it up front like the other send-path secret APIs.

Proposed fix
     pub async fn respond(
         &self,
         chat_jid: impl Into<Jid>,
         event_msg_id: &str,
         event_creator_jid: &Jid,
         message_secret: &[u8],
         response: EventResponseType,
         extra_guest_count: Option<i32>,
     ) -> Result<SendResult, SendError> {
         let chat_jid = &chat_jid.into();
+        if message_secret.len() != 32 {
+            return Err(SendError::InvalidRequest(format!(
+                "message_secret must be exactly 32 bytes, got {}",
+                message_secret.len()
+            )));
+        }
         let my_jid = self.client.get_pn().ok_or(SendError::NotLoggedIn)?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/events.rs` around lines 78 - 104, The message_secret parameter
is caller input that should be validated before encryption. Add validation
upfront to check that message_secret is exactly 32 bytes in length before
calling event::encrypt_event_response_with_secret. If the length is incorrect,
return a SendError that properly indicates a bad request rather than letting the
encryption helper convert it to SendError::Internal. This validation should
occur after the my_base calculation and before the
encrypt_event_response_with_secret call, consistent with how other send-path
secret APIs handle this.
src/features/comments.rs (1)

53-130: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider using a fixed-size array directly for comment_secret to avoid the .expect() call.

Look, I need this code to be bulletproof. The .expect() on line 119 works because you just created a 32-byte Vec three lines above, but this pattern is fragile if someone refactors the secret generation. Direct array initialization eliminates the conversion entirely:

-        let comment_secret: Vec<u8> = {
-            use rand::Rng;
-            let mut secret = vec![0u8; 32];
-            rand::make_rng::<rand::rngs::StdRng>().fill_bytes(&mut secret);
-            secret
-        };
+        let comment_secret: [u8; 32] = {
+            use rand::Rng;
+            let mut secret = [0u8; 32];
+            rand::make_rng::<rand::rngs::StdRng>().fill_bytes(&mut secret);
+            secret
+        };

Then line 116-119 becomes just let secret = comment_secret; - no conversion, no .expect().

As per coding guidelines: "Never use .unwrap() outside tests" - and .expect() is functionally equivalent. This refactor removes the need entirely.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/features/comments.rs` around lines 53 - 130, The `comment_secret` is
created as a Vec<u8> and then converted to a [u8; 32] array using
`.try_into().expect()`, which is fragile and violates the no-.expect()
guideline. Instead, directly initialize `comment_secret` as a fixed-size array
[u8; 32] in the variable declaration by using a fixed-size array literal or the
appropriate array initialization method, then simplify the conversion line to
just assign `comment_secret` directly to `secret` without any conversion or
`.expect()` call.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/client.rs`:
- Around line 312-318: The is_transport_unavailable function does not check for
transport failures wrapped inside the ClientError::Iq variant, which can contain
IqError::NotConnected or send-pipeline failures. Update is_transport_unavailable
to handle the ClientError::Iq variant by inspecting the wrapped IqError to
determine if it represents a transport unavailability condition, ensuring that
wrapped transport failures are properly detected for retry and reconnect logic.

In `@src/features/blocking.rs`:
- Around line 42-50: The resolve_lid_pn function at line 42 requires a PN↔LID
mapping to exist, but the unblock method at line 74 doesn't actually need this
mapping since UpdateBlocklistSpec::unblock only requires a LID. When a caller
provides a LID directly, the unblock path should not fail due to a missing
mapping. Refactor the unblock logic to check if the provided jid is already a
valid LID and use it directly without calling resolve_lid_pn, only attempting
the mapping resolution when necessary for PN jids.

In `@src/features/community.rs`:
- Around line 21-34: Add an `InvalidRequest(String)` variant to the
`CommunityError` enum in the file to maintain consistency with `NewsletterError`
and `GroupError`. Then locate the two usages of `MexError::PayloadParsing` (at
lines 265 and 312) in the community module where validation failures or
malformed responses are handled, and replace them with
`CommunityError::InvalidRequest(...)` with an appropriate error message,
ensuring the error message clearly describes the invalid request or parsing
failure.

In `@src/send.rs`:
- Around line 58-62: The validation errors created in send_message_impl are
being generated as plain anyhow errors instead of typed ClientError variants,
which causes them to fall through to SendError::Internal in the from_anyhow
function. Replace the anyhow invocations that create validation failures in
send_message_impl (particularly in the sections around lines 1332-1343 and
1365-1388) with ClientError::InvalidRequest instead, so they can be properly
downcast in the from_anyhow match expression and converted to the matchable
SendError::InvalidRequest variant.

---

Outside diff comments:
In `@src/features/chat_actions.rs`:
- Around line 754-766: The error conversion in the get_app_state_key call on
line 766 can be simplified by leveraging the #[from] attribute that already
exists on AppStateError::Internal. Instead of using map_err(|e|
AppStateError::Internal(e.into())), you can use the ? operator with a simpler
map_err that converts the error type using the From trait, or simply remove the
explicit conversion if AppStateError::Internal already implements From for
anyhow::Error. This will make the code more concise while maintaining the same
error handling behavior.

In `@src/features/comments.rs`:
- Around line 53-130: The `comment_secret` is created as a Vec<u8> and then
converted to a [u8; 32] array using `.try_into().expect()`, which is fragile and
violates the no-.expect() guideline. Instead, directly initialize
`comment_secret` as a fixed-size array [u8; 32] in the variable declaration by
using a fixed-size array literal or the appropriate array initialization method,
then simplify the conversion line to just assign `comment_secret` directly to
`secret` without any conversion or `.expect()` call.

In `@src/features/events.rs`:
- Around line 78-104: The message_secret parameter is caller input that should
be validated before encryption. Add validation upfront to check that
message_secret is exactly 32 bytes in length before calling
event::encrypt_event_response_with_secret. If the length is incorrect, return a
SendError that properly indicates a bad request rather than letting the
encryption helper convert it to SendError::Internal. This validation should
occur after the my_base calculation and before the
encrypt_event_response_with_secret call, consistent with how other send-path
secret APIs handle this.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: e1037d55-a871-4cb5-92c9-455fcf9dc9bb

📥 Commits

Reviewing files that changed from the base of the PR and between 32a9cbb and 173059d.

📒 Files selected for processing (28)
  • src/bot.rs
  • src/client.rs
  • src/client/context_impl.rs
  • src/client/iq_ops.rs
  • src/client/messaging.rs
  • src/features/blocking.rs
  • src/features/chat_actions.rs
  • src/features/chatstate.rs
  • src/features/comments.rs
  • src/features/community.rs
  • src/features/contacts.rs
  • src/features/events.rs
  • src/features/groups.rs
  • src/features/labels.rs
  • src/features/media_reupload.rs
  • src/features/mod.rs
  • src/features/newsletter.rs
  • src/features/polls.rs
  • src/features/presence.rs
  • src/features/profile.rs
  • src/features/reaction.rs
  • src/features/signal.rs
  • src/features/status.rs
  • src/features/tctoken.rs
  • src/lib.rs
  • src/request.rs
  • src/send.rs
  • tests/bench-integration/src/main.rs

Comment thread src/client.rs
Comment thread src/features/blocking.rs
Comment thread src/features/community.rs
Comment thread src/send.rs Outdated

@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: 1ff4108063

ℹ️ 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/client/messaging.rs

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/send.rs`:
- Around line 59-65: Add a specific match arm for the `ClientError::Iq` variant
in the error downcast block (in the match statement that handles
`err.downcast::<ClientError>()`) to extract and flatten the inner `IqError` to
`SendError::Iq`. This new arm should be placed before the catch-all `Ok(client)`
arm so that `ClientError::Iq` errors are consistently routed to `SendError::Iq`
regardless of how they arrive, ensuring the same error type always maps to the
same `SendError` variant.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0a72d33b-90f3-41c1-885a-aed5f3abf85e

📥 Commits

Reviewing files that changed from the base of the PR and between 173059d and 1ff4108.

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

Comment thread src/send.rs

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 1 file (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread src/send.rs
claude added 4 commits June 18, 2026 00:58
… errors

The public API leaked ~5 inconsistent error families (anyhow::Error,
MexError, PresenceError, IqError, ClientError) with no rule, often mixed
within a single module. This replaces them with one consistent typed error
per feature domain, all built on a shared ClientError base.

- ClientError becomes the DRY transport/connection foundation (adds
  Iq(#[from] IqError) + a transparent anyhow catch-all). Each domain error
  embeds it via #[from]; it is not an umbrella -- the per-domain types stay
  the public return types.
- New/extended per-domain errors (thiserror, #[non_exhaustive]): SendError
  (send path + MessageContext), GroupError, ContactError, StatusError(=SendError),
  BlockingError, ChatStateError, CommunityError, NewsletterError, ProfileError,
  PollError, SignalError, AppStateError (chat actions + labels), TcTokenError,
  MediaReuploadError; PresenceError extended.
- anyhow no longer appears in any migrated public signature; it remains only
  internally for ? plumbing, surfaced through a last-resort transparent
  Internal variant where a typed case does not yet exist.
- Re-export every public domain error + ClientError + IqError at the crate
  root, and add the common ones (ClientError, IqError, SendError) to the prelude.

BREAKING CHANGE: public return types changed across the send path
(send_message/send_text/forward_message/revoke/edit/pin/keep, status),
MessageContext (send_message/reply/reply_quoting/edit_message/revoke_message/
react), and every feature module (groups, contacts, blocking, chatstate,
presence, community, newsletter, profile, polls, events, comments, reactions,
signal, tc_token, media reupload, chat actions, labels).
send_message_impl threads anyhow internally, so its
ClientError::NotLoggedIn was funneled into SendError::Internal via the
blanket #[from] anyhow::Error at the public send wrappers instead of the
matchable SendError::NotLoggedIn. Add SendError::from_anyhow to downcast
the propagated anyhow back to ClientError and route NotLoggedIn (and
other ClientError cases) to their typed variants, applied at the
send_message_impl / require_pn wrapper call sites.
@jlucaso1
jlucaso1 force-pushed the refactor/per-domain-typed-errors branch from 080ed97 to 659a852 Compare June 18, 2026 03:58

@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: 080ed97fe5

ℹ️ 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/features/newsletter.rs Outdated

@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: b0a31b72a8

ℹ️ 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/send.rs

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/client/messaging.rs`:
- Around line 157-160: The error message in the InvalidRequest branch for the
is_newsletter() check in the edit_message_encrypted function provides misleading
guidance by suggesting to use edit_message, which will also fail downstream for
newsletters. Update the error message string to point callers directly to the
correct newsletter edit API endpoint instead of suggesting edit_message,
ensuring the remediation hint actually resolves the issue rather than leading to
another dead-end error.

In `@src/features/signal.rs`:
- Around line 250-259: The error handling in the validate_session method
manually wraps the error with anyhow::anyhow!() which creates unnecessary
indirection since SignalError::Internal already has #[from] anyhow::Error. Use
anyhow's context method instead to add the descriptive message while preserving
the original error chain. Replace the manual anyhow::anyhow!("session check
failed: {e}") call with e.context("session check failed") to make the error
handling more idiomatic and consistent with anyhow's patterns.

In `@src/send.rs`:
- Around line 32-40: Remove the `#[from]` attribute from the `Client` variant
(which currently has the `ClientError` type) to prevent automatic error
conversions that bypass the typed surface. Then implement a manual
`From<ClientError>` trait implementation for `SendError` that properly flattens
`ClientError` conversions—specifically mapping `ClientError::NotLoggedIn`
directly to `SendError::NotLoggedIn` and other `ClientError` variants to
`SendError::Client()`. This mirrors the flattening pattern already implemented
in the `from_anyhow()` method so that using `?` with `ClientError` produces the
correct matchable `SendError` variant instead of wrapping it in the `Client`
enum variant.
- Around line 58-75: The from_anyhow method needs to recover GroupError
exceptions before they collapse into SendError::Internal. Add a new downcast
check for GroupError after the existing IqError check (in the Err(other) branch)
but before the final SendError::Internal fallback. This new check should handle
all four GroupError variants (Iq, Mex, InvalidRequest, and Internal) by mapping
the Iq variant to SendError::Iq and routing the other variants appropriately to
their corresponding SendError equivalents, ensuring GroupError errors follow the
documented SendError::Iq pattern matching path instead of becoming
SendError::Internal.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8666a4a2-9388-441a-8ae4-374fafe783d4

📥 Commits

Reviewing files that changed from the base of the PR and between 1ff4108 and b0a31b7.

📒 Files selected for processing (28)
  • src/bot.rs
  • src/client.rs
  • src/client/context_impl.rs
  • src/client/iq_ops.rs
  • src/client/messaging.rs
  • src/features/blocking.rs
  • src/features/chat_actions.rs
  • src/features/chatstate.rs
  • src/features/comments.rs
  • src/features/community.rs
  • src/features/contacts.rs
  • src/features/events.rs
  • src/features/groups.rs
  • src/features/labels.rs
  • src/features/media_reupload.rs
  • src/features/mod.rs
  • src/features/newsletter.rs
  • src/features/polls.rs
  • src/features/presence.rs
  • src/features/profile.rs
  • src/features/reaction.rs
  • src/features/signal.rs
  • src/features/status.rs
  • src/features/tctoken.rs
  • src/lib.rs
  • src/request.rs
  • src/send.rs
  • tests/bench-integration/src/main.rs

Comment thread src/client/messaging.rs
Comment thread src/features/signal.rs
Comment thread src/send.rs
Comment thread src/send.rs
…dError variants

Close the "typed error masked as SendError::Internal" class at the root by
flattening in every conversion to SendError, not only from_anyhow:

- Drop #[from] on SendError::Client and add a manual From<ClientError> so a
  bare `?` maps NotLoggedIn -> SendError::NotLoggedIn and Iq -> SendError::Iq
  instead of nesting under Client(..).
- Add From<GroupError> for SendError plus a GroupError downcast in from_anyhow
  (before the ClientError check) so a group-metadata IQ failure from
  query_info surfaces as SendError::Iq, not Internal.
- Point the edit_message_encrypted newsletter hint at newsletter().edit_message
  (edit_message also rejects newsletters).
- Use the idiomatic e.context("session check failed") in validate_session.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/client/messaging.rs (2)

106-116: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Typed validation errors collapse to Internal here.

Look, this is exactly the kind of thing that makes me question whether we're actually shipping typed errors or just pretending. send_message_impl returns anyhow::Error and wraps typed SendError::InvalidRequest validation failures (like newsletter rejection at send.rs:1407). Using bare ? here routes everything through #[from] anyhow::Error into SendError::Internal, losing the typed surface.

revoke_message_inner in send.rs:1296 gets this right with .map_err(SendError::from_anyhow)?. This needs the same treatment.

Proposed fix
         self.send_message_impl(
             to,
             &edit_container_message,
             None,
             false,
             false,
             Some(crate::types::message::EditAttribute::MessageEdit),
             vec![],
             None,
         )
-        .await?;
+        .await
+        .map_err(crate::send::SendError::from_anyhow)?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/messaging.rs` around lines 106 - 116, The bare `?` operator on the
`send_message_impl` call causes typed validation errors to collapse to
`SendError::Internal` instead of being properly converted. Replace the `.await?`
with `.await.map_err(SendError::from_anyhow)?` on the `send_message_impl` result
to properly preserve typed errors like `SendError::InvalidRequest` during
conversion from `anyhow::Error`, following the same pattern used in
`revoke_message_inner` in send.rs.

193-203: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Same from_anyhow gap as edit_message_inner.

This is the same issue. If someone calls edit_message_encrypted targeting a newsletter JID, send_message_impl raises a typed SendError::InvalidRequest, but bare ? here loses it to Internal. The upstream validation at line 157 catches the obvious case, but any typed errors from deeper in send_message_impl still get swallowed.

Be consistent with revoke_message_inner.

Proposed fix
         self.send_message_impl(
             to,
             &envelope,
             None,
             false,
             false,
             Some(crate::types::message::EditAttribute::MessageEdit),
             vec![],
             None,
         )
-        .await?;
+        .await
+        .map_err(SendError::from_anyhow)?;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/client/messaging.rs` around lines 193 - 203, The edit_message_encrypted
method is losing typed error information when calling send_message_impl by using
the bare ? operator, which converts typed SendError variants like InvalidRequest
to Internal errors. Examine how revoke_message_inner properly handles errors
from send_message_impl to preserve typed errors (likely using from_anyhow
conversion or similar pattern), then apply the same error handling approach to
the send_message_impl call in edit_message_encrypted to ensure that typed errors
like SendError::InvalidRequest are preserved rather than being converted to
generic Internal errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/client/messaging.rs`:
- Around line 106-116: The bare `?` operator on the `send_message_impl` call
causes typed validation errors to collapse to `SendError::Internal` instead of
being properly converted. Replace the `.await?` with
`.await.map_err(SendError::from_anyhow)?` on the `send_message_impl` result to
properly preserve typed errors like `SendError::InvalidRequest` during
conversion from `anyhow::Error`, following the same pattern used in
`revoke_message_inner` in send.rs.
- Around line 193-203: The edit_message_encrypted method is losing typed error
information when calling send_message_impl by using the bare ? operator, which
converts typed SendError variants like InvalidRequest to Internal errors.
Examine how revoke_message_inner properly handles errors from send_message_impl
to preserve typed errors (likely using from_anyhow conversion or similar
pattern), then apply the same error handling approach to the send_message_impl
call in edit_message_encrypted to ensure that typed errors like
SendError::InvalidRequest are preserved rather than being converted to generic
Internal errors.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4087275f-0181-458b-9fb4-b37ae0b20b7f

📥 Commits

Reviewing files that changed from the base of the PR and between b0a31b7 and c02ee12.

📒 Files selected for processing (3)
  • src/client/messaging.rs
  • src/features/signal.rs
  • src/send.rs

@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: c02ee128d8

ℹ️ 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/features/reaction.rs
edit_message_inner/edit_message_encrypted_inner used a bare `?` on
send_message_impl, collapsing its typed SendError::InvalidRequest
(newsletter/status rejections) into SendError::Internal; route through
from_anyhow like revoke_message_inner does.

resolve_outgoing_addon_parent returned anyhow::Error, so reaction/comment
sends surfaced caller/login failures (missing key id, logged out) as
SendError::Internal. Return SendError directly with InvalidRequest/NotLoggedIn
so callers can match them.

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/message/msg_secret.rs`:
- Around line 798-809: The code currently falls back to using remote_jid when
participant is missing, but for group chats remote_jid is the chat JID not the
author, causing incorrect secret lookups and misleading errors. Before the
ok_or_else call that processes target_key.remote_jid, add a check to reject
group targets when participant is absent and from_me is not true. If these
conditions are met, return a SendError indicating that participant is required
for group messages, preventing the fallback to remote_jid and failing fast with
a clear error message.
🪄 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: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: abbb4ac7-fa9c-40a6-87d0-2975c7d86aeb

📥 Commits

Reviewing files that changed from the base of the PR and between c02ee12 and d20ac09.

📒 Files selected for processing (2)
  • src/client/messaging.rs
  • src/message/msg_secret.rs

Comment thread src/message/msg_secret.rs

@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: d20ac09b28

ℹ️ 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/features/reaction.rs
resolve_outgoing_addon_parent fell back to remote_jid as the author when
participant was absent; for group parents remote_jid is the group JID, not
the sender, so the secret lookup ran under the wrong key and surfaced a
misleading "no secret stored" error. Reject group parents without participant
via InvalidRequest instead.

is_community_announce_group returned anyhow::Error, so a failing group metadata
IQ during send_reaction collapsed to SendError::Internal. Return GroupError so
the send path flattens it to SendError::Iq via From<GroupError>.
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