Skip to content

Matrix: 2-member rooms in groups allowlist incorrectly classified as DMs #17771

@camopel

Description

@camopel

Bug

When a Matrix room has exactly 2 members (e.g. bot + one user), the DM detection heuristic in extensions/matrix/src/matrix/monitor/direct.ts classifies it as a direct message, even if the room is explicitly listed in channels.matrix.groups config.

This causes the room to route to the agent's main session (agent:main:main) instead of getting its own isolated group session (agent:main:matrix:channel:<roomId>).

Steps to Reproduce

  1. Create a Matrix room with a name/alias (e.g. #openclaw:server)
  2. Invite the bot — room now has exactly 2 members
  3. Add the room to channels.matrix.groups allowlist:
    "groups": {
      "!roomId:server": { "allow": true }
    }
  4. Send a message in the room
  5. Observe the message routes to agent:main:main instead of agent:main:matrix:channel:!roomId:server

Root Cause

In direct.ts, isDirectMessage() has three checks:

  1. client.dms.isDm(roomId) — checks m.direct account data
  2. memberCount === 2this catches all 2-person rooms regardless of config
  3. is_direct state flag

Check #2 fires before any config-awareness, so allowlisted group rooms with 2 members are always classified as DMs.

Proposed Fix

Pass the set of configured group room IDs into createDirectRoomTracker. If a room is in that set, skip all DM heuristics and return false immediately.

// direct.ts
type DirectRoomTrackerOptions = {
  log?: (message: string) => void;
  /** Room IDs explicitly configured as groups — never classify these as DMs. */
  configuredGroupRoomIds?: ReadonlySet<string>;
};

// In isDirectMessage(), before any other checks:
if (configuredGroupRoomIds.has(roomId)) {
  log(`matrix: dm check skipped — room in groups config room=${roomId}`);
  return false;
}
// index.ts — when creating the tracker:
const directTracker = createDirectRoomTracker(client, {
  log: logVerboseMessage,
  configuredGroupRoomIds: new Set(
    roomsConfig ? Object.keys(roomsConfig).filter((k) => k !== "*") : [],
  ),
});

Patch is minimal (2 files), backward compatible, and TypeScript compiles clean.

Environment

  • OpenClaw 2026.2.15 (local build from main)
  • Matrix plugin from extensions/matrix
  • Self-hosted homeserver via Tailscale

Metadata

Metadata

Assignees

No one assigned

    Labels

    staleMarked as stale due to inactivity

    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