Skip to content

Mattermost: chat_type is non-deterministic for private channels (channel-type vs target-string inference); inferTargetChatType hook not implemented #95646

Description

@iloveleon19

Summary

The chat_type OpenClaw reports for a Mattermost private channel is non-deterministic across code paths: it is group when derived from the channel type, but channel when derived from the delivery target string. The same conversation can therefore be keyed under two different session namespaces.

Related: #95645 — the system-prompt-intro half of the same "group" overload. This issue is the deeper derivation/routing half.

Two derivation strategies that disagree

// By channel type — AUTHORITATIVE, correct (monitor-auth-*.js / channel.runtime-*.js)
mapMattermostChannelKind(type):  D -> direct ;  G or P -> group ;  else (O) -> channel

// By target-string prefix — LOSSY (targets-*.js / subagent-announce-delivery-*.js)
inferChatTypeFromTarget({to}):  /^user:/ -> direct ; /^(channel:|thread:)/ -> channel ; /^group:/ -> group

A private channel (P) is addressed as channel:<id> — the same prefix as a public channel — yet its authoritative type is group. So the prefix path returns channel while the type path returns group. Mattermost's own resolveDeliveryTarget always emits to: channel:${id} (even for private channels), which is the origin of the lossy prefix.

OpenClaw already provides a per-plugin extension point for exactly this — messaging.inferTargetChatType({to}) — and Telegram, Discord, Signal, iMessage and ClickClack implement it. Mattermost does not, so its target → chat_type inference falls back to the lossy generic default. (Also note: the generic inferChatTypeFromTarget returns at the channel: rule before it would reach the plugin hook, so even implementing the Mattermost hook needs the generic order changed too.)

Telegram is a clean model: it carries chatType on the route target (derived from the authoritative id), instead of re-deriving it from a string later.

Impact

A wrong chat_type mis-addresses the conversation. With session keys
agent:<id>:mattermost:<direct|channel|group>:<peerId>:thread:<root>, a private channel ends up under both
group:<id>:thread:<root> (inbound, type-based) and a phantom channel:<id>:thread:<root> (delivery,
prefix-based). Threaded/scheduled deliveries bound to one namespace then fail to match the other (fail-closed
delivery, or a conversation split across two session keys).

Repro

In a Mattermost private channel, observe the same thread keyed under both group:<id>:thread:<root>
(inbound classification) and channel:<id>:thread:<root> (a delivery-side session).

Proposed fix

  1. Implement Mattermost's messaging.inferTargetChatType hook, resolving channel-vs-group from the real
    channel type — the pieces already exist (mapMattermostChannelTypeToChatType,
    resolveMattermostTrustedChatKind, and a TTL channelCache).
  2. Make the generic inferChatTypeFromTarget / inferDeliveryTargetChatType consult the plugin hook
    before the channel: ⇒ channel default
    (today the channel: rule returns first, so the hook is never
    reached for a channel: target). Or adopt Telegram's "carry chatType on the route target" pattern so no
    re-inference from a lossy string is needed.

Note

We confirmed the type-resolution approach end-to-end downstream: resolving the channel type via
GET /api/v4/channels/<id> (O→channel, P/G→group, D→direct) and using that instead of the
target-string inference fixes the mis-classification.

Environment

ghcr.io/openclaw/openclaw:2026.6.5. Relevant dist files: monitor-auth-*.js, channel.runtime-*.js,
channel-plugin-runtime-*.js, targets-*.js, subagent-announce-delivery-*.js.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Normal backlog priority with limited blast radius.clawsweeper:fix-shape-clearClawSweeper found a clear likely implementation shape for this issue.clawsweeper:queueable-fixClawSweeper marked this issue as an existing queue_fix_pr work candidate.clawsweeper:source-reproClawSweeper found a high-confidence source-level issue reproduction.impact:message-lossChannel message delivery can be lost, duplicated, or misrouted.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.no-staleExclude from stale automation

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions