Skip to content

fix(whatsapp): use per-channel-peer dmScope for inbound direct messages#76314

Closed
chinar-amrutkar wants to merge 3 commits into
openclaw:mainfrom
chinar-amrutkar:fix/76263-whatsapp-per-chat-session
Closed

fix(whatsapp): use per-channel-peer dmScope for inbound direct messages#76314
chinar-amrutkar wants to merge 3 commits into
openclaw:mainfrom
chinar-amrutkar:fix/76263-whatsapp-per-chat-session

Conversation

@chinar-amrutkar

Copy link
Copy Markdown
Contributor

Summary

Describe the problem and fix in 2–5 bullets:

  • Problem: WhatsApp plugin routes all inbound direct messages to the same agent:${agentId}:main session key regardless of contact, causing distinct chats to share session/.jsonl files and model context.
  • Why it matters: Multi-tenant SaaS setups with one WhatsApp account per agent lose conversation isolation between contacts.
  • What changed: Added dmScopeOverride to ResolveAgentRouteInput so callers can force per-chat session scoping; WhatsApp now passes dmScopeOverride: "per-channel-peer" when resolving inbound routes.
  • What did NOT change: Group chat routing already uses resolveWhatsAppGroupSessionRoute with proper group scoping.

Change Type

  • Bug fix
  • Refactor required for the fix

Scope

  • Integrations

Linked Issue/PR

Root Cause

  • Root cause: resolveAgentRoute uses cfg.session?.dmScope ?? "main". With "main", all direct chats collapse to agent:${agentId}:main regardless of peer. WhatsApp passed a correct peer object but the global dmScope was the bottleneck.
  • Missing detection: No guardrail existed to prevent channel plugins from inheriting a globally-shared dmScope when they require per-chat isolation.
  • Contributing context: Telegram explicitly passes dmScope: "per-account-channel-peer" and works correctly. WhatsApp had no analogous override.

Regression Test Plan

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient (routing + session-key tests cover the mechanism; WhatsApp specific integration test would catch the behavior)
  • Target test or file: extensions/whatsapp/src/auto-reply/monitor/on-message.ts — add integration test verifying distinct peerId values produce distinct route.sessionKey values
  • Scenario the test should lock in: Two inbound direct messages from different E.164 numbers → sessionKey values differ and contain the peer ID
  • Existing test that already covers this (if any): src/routing/session-key.ts tests for dmScope: "per-channel-peer" exist but no WhatsApp-specific seam test
  • If no new test added, why not: The fix is minimal and follows the established Telegram pattern; a WhatsApp integration test would be a separate work item

User-Visible / Behavior Changes

  • WhatsApp direct messages now get chat-scoped session keys: agent:${agentId}:whatsapp:direct:${peerId} instead of agent:${agentId}:main
  • Existing sessions are not migrated — new messages from a given contact will use a fresh scoped key; old main session continues independently

@openclaw-barnacle openclaw-barnacle Bot added channel: whatsapp-web Channel integration: whatsapp-web agents Agent runtime and tooling size: XS labels May 2, 2026
@clawsweeper

clawsweeper Bot commented May 2, 2026

Copy link
Copy Markdown
Contributor

Thanks for the context here. I swept through the related work, and this is now duplicate or superseded.

Close as superseded: the same WhatsApp direct-message isolation work is now covered by the open account-aware fix at #76730, and that branch addresses the account-id gap in this PR.

Canonical path: Close this branch and continue review of the account-aware fix at #76730; close the linked issue only after that fix lands.

So I’m closing this here and keeping the remaining discussion on #76730.

Review details

Best possible solution:

Close this branch and continue review of the account-aware fix at #76730; close the linked issue only after that fix lands.

Do we have a high-confidence way to reproduce the issue?

Yes. Source inspection on current main shows WhatsApp direct inbound messages pass a peer to resolveAgentRoute, the resolver falls back to dmScope "main", and direct peer session-key generation returns the agent main key under that scope.

Is this the best way to solve the issue?

No. This PR uses per-channel-peer, which fixes separate contacts on one WhatsApp account but omits accountId; the account-aware per-account-channel-peer approach in the canonical PR is the safer fix.

Security review:

Security review needs attention: The branch narrows the existing context leak but leaves an account-crossing WhatsApp DM isolation gap because the chosen scope omits accountId.

  • [medium] Account-agnostic WhatsApp DM session keys — extensions/whatsapp/src/auto-reply/monitor/on-message.ts:94
    Using per-channel-peer means the session key does not include the WhatsApp account id, so the same peer across separate WhatsApp accounts can still share transcript and model context when routed to the same agent.
    Confidence: 0.9

What I checked:

Likely related people:

  • steipete: Authored the open account-aware WhatsApp isolation PR that supersedes this branch and touches the same routing, docs, tests, and last-route files. (role: canonical follow-up author and recent area contributor; confidence: high; commits: 6523eb7618b4; files: extensions/whatsapp/src/auto-reply/monitor/on-message.ts, extensions/whatsapp/src/auto-reply/monitor/inbound-dispatch.ts, src/routing/resolve-route.ts)
  • mcaxtr: Merged work in PR fix(whatsapp): isolate multi-account inbound state and align shared defaults #65700 recently changed WhatsApp multi-account inbound state and scoped session behavior in the same plugin area. (role: WhatsApp session/account behavior contributor; confidence: medium; commits: 458a52610a4d; files: extensions/whatsapp/src/auto-reply/monitor/on-message.ts, extensions/whatsapp/src/auto-reply/monitor/process-message.ts, extensions/whatsapp/src/group-session-key.ts)
  • jarvis-sam: PR feat(routing): add per-account-channel-peer session scope #3095 introduced the per-account-channel-peer session scope used by the stronger canonical fix. (role: routing scope feature contributor; confidence: medium; commits: b6a3a91edf52, d499b148423e; files: src/routing/session-key.ts, src/routing/resolve-route.ts, src/config/zod-schema.session.ts)

Codex review notes: model gpt-5.5, reasoning high; reviewed against 333f65fc8a12.

@chinar-amrutkar
chinar-amrutkar force-pushed the fix/76263-whatsapp-per-chat-session branch from 46e453f to 7181de4 Compare May 2, 2026 23:47
@openclaw-barnacle openclaw-barnacle Bot removed the agents Agent runtime and tooling label May 2, 2026
@chinar-amrutkar
chinar-amrutkar force-pushed the fix/76263-whatsapp-per-chat-session branch from 7181de4 to e736116 Compare May 3, 2026 00:00
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation agents Agent runtime and tooling size: S and removed size: XS labels May 3, 2026
Distinct WhatsApp contacts were colliding on the same :main session key,
causing shared .jsonl files and interleaved model context.

- Added dmScopeOverride to ResolveAgentRouteInput so callers can force a
  specific dmScope regardless of the global config setting
- WhatsApp now passes dmScopeOverride: "per-channel-peer" when resolving
  inbound routes, producing session keys of the form
  agent:{agentId}:whatsapp:direct:{peerId}
- Added regression test verifying distinct peerId values produce distinct
  route.sessionKey values

Closes openclaw#76263
@chinar-amrutkar
chinar-amrutkar force-pushed the fix/76263-whatsapp-per-chat-session branch from e736116 to b4e160e Compare May 3, 2026 00:12
@openclaw-barnacle openclaw-barnacle Bot removed the agents Agent runtime and tooling label May 3, 2026
When using per-channel-peer dmScope, updateWhatsAppMainLastRoute now
records the last-route against the per-contact sessionKey instead of
skipping it. This ensures deliveryContext metadata is persisted for
later session deliveries.

Added test: "records last-route for per-contact session when using scoped dmScope"
… guard

The pin guard (pinnedMainDmRecipient) applies only to main session updates.
Per-contact sessions should always record their last-route since they are
independent of the main session pin state.

Updated test to use pinnedMainDmRecipient that differs from dmRouteTarget
to verify the per-contact session is updated even when not the pinned owner.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: whatsapp-web Channel integration: whatsapp-web docs Improvements or additions to documentation size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: WhatsApp plugin uses single agent:${id}:main lane for all chats — distinct contacts share session/jsonl/context

1 participant