Feat/gmail unsubscribe agent#1657
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughExtracts List-Unsubscribe into EmailMessage, adds GmailUnsubscribeTool that returns a pending-approval unsubscribe action, registers the tool, and adds a frontend UnsubscribeApprovalCard that asks for user approval and calls core RPC to execute the unsubscribe. ChangesGmail Unsubscribe Workflow
sequenceDiagram
participant User
participant Frontend as UnsubscribeApprovalCard
participant RPC as callCoreRpc
participant Backend as GmailUnsubscribeTool
User->>Frontend: Clicks "Approve & Unsubscribe"
Frontend->>RPC: callCoreRpc('tools::execute_unsubscribe', {link})
RPC->>Backend: execute_unsubscribe request
Backend-->>RPC: pending_approval / error response
RPC-->>Frontend: reply
Frontend-->>User: show approved or revert to pending on error
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Tip 💬 Introducing Slack Agent: The best way for teams to turn conversations into code.Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
app/src/components/chat/UnsubscribeApprovalCard.tsx (1)
17-18: ⚡ Quick winLocal status can become stale across payload changes.
statusis initialized once and not synchronized to a new pending approval payload. If this component is reused (not remounted), a priorapproved/deniedstate can suppress actions for the next request.Please verify the parent render path keys/remounts this card per pending action. If not, reset state on payload identity change (e.g., action/request id key) or derive status from payload-driven state.
Also applies to: 43-43
🤖 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 `@app/src/components/chat/UnsubscribeApprovalCard.tsx` around lines 17 - 18, The local `status` and `isProcessing` state in UnsubscribeApprovalCard (the `status`, `setStatus`, `isProcessing`, `setIsProcessing` useState hooks) can become stale when a new pending approval payload is rendered into the same mounted component; add logic to synchronize/reset state on payload identity changes (e.g., watch the approval/request id prop or the payload object) by resetting `setStatus('pending')` (and `setIsProcessing(false)` if appropriate) in a useEffect that depends on that id/identity, or alternatively derive `status` from the incoming payload instead of local state so a prior `approved`/`denied` value cannot suppress actions for the next request.
🤖 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 `@app/src/components/chat/UnsubscribeApprovalCard.tsx`:
- Around line 20-29: handleApprove can be re-entered on rapid clicks; add a
short-circuit at the start of handleApprove to return immediately if
isProcessing is true (and optionally if status === 'approved') to prevent
dispatching duplicate RPCs, then proceed to setIsProcessing(true) and call
callCoreRpc as before — reference handleApprove, isProcessing, setIsProcessing,
callCoreRpc, and status to locate and implement the guard.
In `@src/openhuman/tools/impl/network/gmail_unsubscribe.rs`:
- Around line 38-65: Add verbose, grep-friendly diagnostic logging to the async
fn execute to improve auditability: log entry at the start of execute (use
tracing::debug or log::debug with a stable prefix like
"GMAIL_UNSUBSCRIBE:ENTRY") including only a redacted sender (e.g., replace
local-part with "***" or show domain only), log the empty-link validation branch
before returning ToolResult::error with a prefix like
"GMAIL_UNSUBSCRIBE:VALIDATION:EMPTY_LINK", and log the pending-approval return
just before returning ToolResult::json with a prefix like
"GMAIL_UNSUBSCRIBE:PENDING_APPROVAL" and include only non-PII metadata (sender
redacted, unsubscribe_link omitted or safely hashed, and action/status fields)
so logs are grep-friendly and contain safe audit metadata; use tracing or log at
debug/trace level per project guidelines and keep message format consistent for
automated parsing.
---
Nitpick comments:
In `@app/src/components/chat/UnsubscribeApprovalCard.tsx`:
- Around line 17-18: The local `status` and `isProcessing` state in
UnsubscribeApprovalCard (the `status`, `setStatus`, `isProcessing`,
`setIsProcessing` useState hooks) can become stale when a new pending approval
payload is rendered into the same mounted component; add logic to
synchronize/reset state on payload identity changes (e.g., watch the
approval/request id prop or the payload object) by resetting
`setStatus('pending')` (and `setIsProcessing(false)` if appropriate) in a
useEffect that depends on that id/identity, or alternatively derive `status`
from the incoming payload instead of local state so a prior `approved`/`denied`
value cannot suppress actions for the next request.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6c336201-209d-460b-9414-717773569f90
📒 Files selected for processing (7)
app/src/components/chat/UnsubscribeApprovalCard.tsxsrc/openhuman/composio/providers/gmail/ingest.rssrc/openhuman/composio/providers/gmail/post_process.rssrc/openhuman/memory/tree/canonicalize/email.rssrc/openhuman/tools/impl/network/gmail_unsubscribe.rssrc/openhuman/tools/impl/network/mod.rssrc/openhuman/tools/ops.rs
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
app/src/components/chat/__tests__/UnsubscribeApprovalCard.test.tsx (1)
8-9: ⚡ Quick winAdd mock cleanup for test isolation.
The
callCoreRpcmock is defined at module level and shared across all tests. WhilemockResolvedValueOnceandmockRejectedValueOnceisolate behavior per call, the mock's call history accumulates across tests, creating hidden global state that could affect test determinism.🧹 Add beforeEach to reset mocks
vi.mock('../../../services/coreRpcClient', () => ({ callCoreRpc: vi.fn() })); describe('UnsubscribeApprovalCard', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + const mockPayload = {As per coding guidelines, keep unit tests deterministic and avoid hidden global state.
🤖 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 `@app/src/components/chat/__tests__/UnsubscribeApprovalCard.test.tsx` around lines 8 - 9, Add a beforeEach hook to reset the shared mock state so call histories don't leak between tests: the module-level vi.mock of ../../../services/coreRpcClient that exposes callCoreRpc (a vi.fn()) accumulates call history, so add a beforeEach(() => vi.clearAllMocks() or vi.resetAllMocks()) in UnsubscribeApprovalCard.test.tsx to clear calls/implementations before each test, ensuring test isolation for mocks like callCoreRpc.
🤖 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 `@app/src/components/chat/__tests__/UnsubscribeApprovalCard.test.tsx`:
- Around line 28-32: Add a new unit test in UnsubscribeApprovalCard.test.tsx
that mirrors the existing "action" guard test but verifies the status guard:
create a payload by spreading mockPayload and setting status to 'completed' (or
any value other than 'pending_approval'), render <UnsubscribeApprovalCard
payload={payload} /> using renderWithProviders, and assert that container is an
empty DOM element; follow the same test structure and naming convention as the
existing "returns null if action is not unsubscribe" test to ensure both guards
(action and status) are covered.
---
Nitpick comments:
In `@app/src/components/chat/__tests__/UnsubscribeApprovalCard.test.tsx`:
- Around line 8-9: Add a beforeEach hook to reset the shared mock state so call
histories don't leak between tests: the module-level vi.mock of
../../../services/coreRpcClient that exposes callCoreRpc (a vi.fn()) accumulates
call history, so add a beforeEach(() => vi.clearAllMocks() or
vi.resetAllMocks()) in UnsubscribeApprovalCard.test.tsx to clear
calls/implementations before each test, ensuring test isolation for mocks like
callCoreRpc.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 0ba1ce7b-01db-4b6b-9508-876c664c3637
📒 Files selected for processing (3)
app/src/components/chat/UnsubscribeApprovalCard.tsxapp/src/components/chat/__tests__/UnsubscribeApprovalCard.test.tsxsrc/openhuman/tools/impl/network/gmail_unsubscribe.rs
🚧 Files skipped from review as they are similar to previous changes (2)
- src/openhuman/tools/impl/network/gmail_unsubscribe.rs
- app/src/components/chat/UnsubscribeApprovalCard.tsx
…unused import issues
…bit nits - Add missing `list_unsubscribe: None` to EmailMessage literals in `tests/agent_retrieval_e2e.rs` and the `pick_thread_subject_*` unit tests in `composio/providers/gmail/ingest.rs` so the workspace tests compile against the new field added to `EmailMessage`. - UnsubscribeApprovalCard.test.tsx: clear shared `callCoreRpc` mock state between tests (`beforeEach(vi.clearAllMocks)`) and add a guard test asserting the card renders null when `status !== 'pending_approval'`.
Summary
List-Unsubscribeheaders from raw Gmail payloads within the Composio ingest pipeline.list_unsubscribemetadata to the canonicalizedEmailMessagestruct and the agent's markdown render tree.GmailUnsubscribeToolto securely intercept agent unsubscribe intents.UnsubscribeApprovalCardReact component to manage user-approved tool executions in the chat UI.Problem
mailto:or HTTPS unsubscribe links from email bodies introduces a critical security risk (accidental unsubscriptions or malicious link execution).Solution
List-Unsubscribeheaders directly during email ingestion, avoiding fragile regex parsing on the email body.gmail_unsubscribetool utilizes thePendingActionpattern, immediately halting execution and returning apending_approvalstate.UnsubscribeApprovalCardUI, guaranteeing that the agent cannot proceed with network requests until the user explicitly clicks "Approve".Submission Checklist
N/A: Behavior-only addition; relies on existing tool registry tests.N/A: Offloaded to CI diff-cover gating.N/A: behaviour-only change## RelatedN/ACloses #NNNin the## RelatedsectionImpact
Related
AI Authored PR Metadata (required for Codex/Linear PRs)
Linear Issue
N/AN/ACommit & Branch
feat/gmail-unsubscribe-agentHEADValidation Run
pnpm --filter openhuman-app format:checkpnpm typecheckN/ABlocked locallyN/AValidation Blocked
command:cargo checkerror:Unable to find libclang: couldn't find any valid shared libraries matching clang.dllimpact:Missing local LLVM/ninja build dependencies forwhisper-rs-syson Windows environment. Offloading Rust checks and compilation to GitHub Actions CI pipeline.Behavior Changes
List-Unsubscribeheaders and prompt users to unsubscribe.Parity Contract
pending_approvalguardrail actively enforced before remote execution.Duplicate / Superseded PR Handling
N/AN/AN/ASummary by CodeRabbit
New Features
Tests