Skip to content

[Bug]: Matrix rooms treated as DM when exactly 2 members #56599

@justsonic

Description

@justsonic

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

Matrix plugin classifies all 2-member rooms as DMs regardless of room type, making it impossible to have separate sessions for any group room that happens to have exactly 2 members (e.g. rooms in a Space, small team rooms, or topic-specific rooms). The isStrictDirectMembership function in the plugin uses a hard-coded joinedMembers.length === 2 check with no config override, so even rooms explicitly created as group rooms (with is_direct: false and no m.direct mapping) are treated as direct messages.

This causes all 2-person rooms to share a single DM session key (agent:main:matrix:direct:@user:server), even when session.dmScope is set to per-channel-peer.

Steps to reproduce

  1. Create a Matrix Space with multiple rooms (e.g. "Work", "Health", "Finance")
  2. Each room has exactly 2 members: a human user and the OpenClaw bot
  3. Rooms are created as regular rooms (not DMs) — is_direct: false on member events, no m.direct account data
  4. Configure session.dmScope: "per-channel-peer" in OpenClaw config
  5. Optionally add rooms to channels.matrix.groups config
  6. Send messages in different rooms
  7. Run /status in each room — all show the same session key: agent:main:matrix:direct:@user:server

Expected behavior

Each room in the Space should have its own session with a unique session key like agent:main:matrix:group:!roomId:server, similar to how Telegram topics each get their own session (agent:main:telegram:group:-xxx:topic:N).

Adding rooms to channels.matrix.groups config or having is_direct: false on room membership should be sufficient to classify them as group rooms.

Actual behavior

All rooms share one session because isStrictDirectMembership() in send-jLbjFm5r.js returns true whenever joinedMembers.length === 2:

function isStrictDirectMembership(params) {
    const joinedMembers = params.joinedMembers ?? [];
    return Boolean(selfUserId && remoteUserId && 
        joinedMembers.length === 2 && 
        joinedMembers.includes(selfUserId) && 
        joinedMembers.includes(remoteUserId));
}

The isDirectMessage function in monitor-B7lcmiuj.js calls this as a fallback even when m.direct has no mapping for the room:

// Even if m.direct check fails, this fallback catches all 2-member rooms:
if (isStrictDirectMembership({ selfUserId, remoteUserId: senderId, joinedMembers })) {
    log(`matrix: dm detected via exact 2-member room`);
    return true;
}

The channels.matrix.groups config has no effect on this classification — it only controls access policy, not session routing.

OpenClaw version

2026.3.24 (cff6dc9)

Operating system

macOS 26.4 (arm64, Apple Silicon)

Install method

No response

Model

anthropic/claude-opus-4-6

Provider / routing chain

Direct API → Anthropic (no proxy/gateway/router). Matrix channel: OpenClaw stock plugin → Caddy reverse proxy (localhost:8008) → Continuwuity v0.5.6 homeserver

Additional provider/model setup details

No response

Logs, screenshots, and evidence

Impact and severity

Medium — workaround exists (add a third member to each room), but it's a hack. The core issue is that the plugin has no way to distinguish between actual DMs and small group rooms that happen to have 2 members.

Additional information

The 2-member DM heuristic makes sense as a fallback for actual DMs where m.direct is missing. But it should be overridable when rooms are explicitly configured in channels.matrix.groups, which is a strong signal that the operator intends them to be treated as groups.

Suggested fix

Add a config option to override DM detection for specific rooms. For example:

{
  channels: {
    matrix: {
      groups: {
        "!roomId:server": {
          forceGroup: true,  // Skip DM detection, always treat as group
          requireMention: false
        }
      }
    }
  }
}

When forceGroup: true is set for a room in the groups config, the isDirectMessage check should return false for that room, regardless of member count.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingbug:behaviorIncorrect behavior without a crash

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions