Skip to content

fix(openclaw): fall back to sessionKey when ctx.channelId is a provider name#871

Closed
octo-patch wants to merge 2 commits intovectorize-io:mainfrom
octo-patch:fix/issue-854-openclaw-channelid-provider-name-fallback
Closed

fix(openclaw): fall back to sessionKey when ctx.channelId is a provider name#871
octo-patch wants to merge 2 commits intovectorize-io:mainfrom
octo-patch:fix/issue-854-openclaw-channelid-provider-name-fallback

Conversation

@octo-patch
Copy link
Copy Markdown
Contributor

Fixes #854

Problem

Some OpenClaw providers (e.g. Discord) populate ctx.channelId with the platform/provider name (e.g. "discord") rather than the actual channel identifier (e.g. "1472750640760623226"). Because ctx.channelId has priority over the sessionKey fallback in deriveBankId(), all Discord channel memories merged into a single bank (main::discord) regardless of which channel was active — defeating per-channel isolation for users with dynamicBankGranularity: ["agent", "channel"].

Debug output from the issue:

[Hindsight] before_prompt_build - bank: main::discord, channel: undefined/undefined

Solution

In deriveBankId(), check if ctx.channelId exactly matches a known provider/platform name (discord, telegram, slack, whatsapp, signal, matrix, irc, line). If it does, treat the value as missing and fall through to the sessionParsed.channel derived from the sessionKey, which correctly carries the real channel identifier.

Only exact case-insensitive matches are filtered — channel names like "slack-general" or "my-discord-bot" pass through unchanged.

Testing

  • Added two new unit tests to derive-bank-id.test.ts:
    1. Verifies that a Discord context where ctx.channelId = "discord" uses the channel extracted from the sessionKey instead.
    2. Verifies that a channel ID like "slack-general" (which contains a provider name as substring) is preserved correctly.
  • All 68 existing tests continue to pass.

MCP tool bridges (Claude Code, Cursor, etc.) sometimes serialize JSON
arrays and objects as strings during transport, causing Pydantic to
reject the input with a validation error. The agent then retries with
the same broken format, wasting tokens and silently losing memories.

Add defensive coercion at two layers:

HTTP API (http.py):
- RecallRequest: field_validator on types and tags (mode="before") that
  parses JSON-string arrays back into lists
- MemoryItem: field_validator on metadata (mode="before") that parses
  JSON-string objects back into dicts (tags coercion was already present)

MCP tools (mcp_tools.py):
- build_content_dict: coerce metadata from JSON string to dict, matching
  the existing tags coercion in the same function
- both recall() variants: coerce types and tags from JSON strings before
  passing to the memory engine

All coercions are backward-compatible — correctly-typed inputs pass through
unchanged.

Fixes vectorize-io#849
…er name (fixes vectorize-io#854)

Some OpenClaw providers (e.g. Discord) populate ctx.channelId with the
platform/provider name (e.g. "discord") rather than the actual channel
identifier. This caused all Discord channel memories to merge into a
single bank regardless of which channel was active, defeating per-channel
isolation configured via dynamicBankGranularity.

The fix detects when ctx.channelId matches a known provider name
(discord, telegram, slack, etc.) and treats it as missing, allowing the
sessionKey fallback parser to supply the real channel ID.
Copy link
Copy Markdown
Collaborator

@nicoloboschi nicoloboschi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, except for the API files changed, can you keep the PR only for openclaw? thanks

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.

OpenClaw plugin: ctx.channelId returns provider name instead of channel ID for Discord sessions

2 participants