Skip to content

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

Description

@matirossi93

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

Summary

The WhatsApp plugin does not construct a chat-scoped sessionKey for inbound messages. Every conversation across distinct contacts of the same WhatsApp account is routed into the default lane agent:${agentId}:main, which means every contact shares the same sessionId, the same .jsonl, and the same model context. The Telegram plugin handles this correctly: each chat gets its own scoped lane.

The practical effect for SaaS / multi-customer deployments: a model that just answered customer A is replying to customer B with A's history still in its context window. So far we've only seen mild manifestations (cache buildup, ~10% extra cost), but cross-customer leak is one short follow-up question away from happening (e.g. customer B asks "y eso?" / "confirmas?" and the model answers based on what customer A asked).

Affects v2026.4.26 and confirmed unchanged in v2026.4.29 (latest).

Reproduction

Multi-tenant SaaS profile with one WhatsApp account routed to one agent. Two distinct contacts (e.g. +5491111111111 and +5491122222222) message the same account within minutes.

  1. Inspect ~/.openclaw-saas/agents/<agent-id>/sessions/sessions.json after both contacts wrote.
  2. Only one entry exists for the WA channel: agent:<agent-id>:main. Its deliveryContext.to (or recent from) shows whichever contact wrote last.
  3. Inspect the corresponding .jsonl file. Both contacts' turns are interleaved in the same file. The model's cacheRead grows turn over turn including the previous customer's content.

Compare with Telegram: messaging the same agent from two distinct Telegram users yields two separate entries (agent:<agent-id>:telegram:slash:<chatId1> and :<chatId2>), each with its own sessionId and its own .jsonl.

Expected behavior

Each WhatsApp chat (per chat_id / sender E.164, plus group ID for groups) should resolve to its own scoped sessionKey, similar to:

agent:${agentId}:whatsapp:direct:${e164}
agent:${agentId}:whatsapp:group:${groupId}

This matches the Telegram plugin pattern and would give per-contact context isolation by default.

Code reference (binary inspection, v2026.4.26)

Telegram does scope by chat:

// dist/extensions/telegram/bot-msflwCEW.js
const sessionKey = params.sessionKey ?? `agent:${agentId}:telegram:group:${buildTelegramGroupPeerId(params.chatId, params.messageThreadId)}`;

The default fallback in dist/session-utils-r05OsxB5.js builds:

`agent:${params.agentId}:main`
`agent:${legacyAgentId}:main`

Searching the WhatsApp plugin (dist/extensions/whatsapp/) does not show an analogous chat-scoped construction — session.runtime, channel.runtime, reply-resolver.runtime, and outbound-adapter do not build a sessionKey containing chat_id / E.164 / group ID. Inbound therefore falls through to the default :main key.

There is also no schema option for it: dist/zod-schema.providers-whatsapp-*.js and plugin-sdk/src/config/types.whatsapp.d.ts expose no lanePerChat / sessionScope flag.

Suggested fix

In the WhatsApp plugin's inbound resolution path (the spot that calls into core.channel.routing.resolveAgentRoute(...) for incoming messages), construct a sessionKey analogous to Telegram's, e.g.:

const e164 = normalizeToE164(params.chatId);
const sessionKey = params.sessionKey ?? (
  params.isGroup
    ? `agent:${agentId}:whatsapp:group:${params.groupId}`
    : `agent:${agentId}:whatsapp:direct:${e164}`
);

Optionally guard behind a config flag (default off) for one or two minor releases to avoid breaking existing deployments that intentionally rely on the single-lane behavior.

Workarounds

For deployments hit by this:

  • Add a SOUL-level rule that detects sender_id change in runtime context and treats the turn as a fresh conversation (defense-in-depth, not a real fix — in-context still wins).
  • A local wrapper that clears the lane / forks a new sessionId whenever sender_id changes is feasible but fragile.

Environment

  • openclaw v2026.4.26 (also confirmed unchanged in v2026.4.29 by tarball grep).
  • Profile: saas with multiple agents bound by { channel: "whatsapp", accountId: "<tenant>" }.
  • Plugins loaded: telegram + bundled whatsapp.
  • Node 20.x, Linux (Ubuntu 22.04, ARM64).

Happy to share more snippets from the binary or a sanitized sessions.json if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.clawsweeper:linked-pr-openClawSweeper found an open linked pull request for this issue.clawsweeper:needs-product-decisionClawSweeper marked this issue as needing a product or behavior decision.clawsweeper:needs-security-reviewClawSweeper marked this issue as needing security-sensitive review.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:securitySecurity boundary, credential, authz, sandbox, or sensitive-data risk.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🦞 diamond lobsterVery strong issue quality with high-confidence source-level or clear reproduction.staleMarked as stale due to inactivity

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions