openclaw - 💡(How to fix) Fix bug(matrix): isStrictDirectMembership classifies 2-person rooms as DMs regardless of is_direct=false or groups[] config — requireMention silently bypassed

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

extensions/matrix/src/matrix/direct-room.ts classifies any 2-member room (bot + 1 user) as a "strict direct room" using a pure member-count heuristic. This happens before per-room/per-group config is consulted, so:

  • A room explicitly configured under channels.matrix.groups[<roomId>] with requireMention: true is still treated as a DM at runtime.
  • A room created with is_direct: false and a name is still treated as a DM.
  • DM mode bypasses requireMention, so the bot responds to every message in those rooms regardless of mention status.

The CHANGELOG entry from #57124 ("honor explicit local is_direct: false for discovered DM candidates") was a partial fix — only for inherited/discovered DM candidates, not for the runtime isStrictDirectRoom path that drives mention/skill/session routing.

Root Cause

The pattern "private 1:1 room with the bot that should still require mentions" is reasonable: a personal scratch room for an admin, a held-aside room before adding family/team members, an OPS room where the user wants explicit invocation. The current code makes this impossible without inviting a placeholder third user.

The deeper issue: groups[<roomId>] config has no effect for any 2-person room. The schema's rooms vs groups distinction is silently broken for the smallest rooms.

Fix Action

Fix / Workaround

The workaround "add a third member" confirms the diagnosis: the 2-person count is the sole DM classifier.

Suggested minimal patch in inspectMatrixDirectRoomEvidence:

Code Example

// extensions/matrix/src/matrix/direct-room.ts
export function isStrictDirectMembership(params: {
  selfUserId?: string | null;
  remoteUserId?: string | null;
  joinedMembers?: readonly string[] | null;
}): boolean {
  // ...
  return Boolean(
    selfUserId &&
    remoteUserId &&
    joinedMembers.length === 2 &&        // ← pure heuristic
    joinedMembers.includes(selfUserId) &&
    joinedMembers.includes(remoteUserId),
  );
}

---

"channels.matrix": {
     "enabled": true,
     "encryption": true,
     "dm": {
       "enabled": true,
       "policy": "allowlist",
       "allowFrom": ["@user:server"],
       "sessionScope": "per-room"
     },
     "groupPolicy": "open",
     "groups": {
       "!roomId:server": {
         "requireMention": true,
         "skills": ["..."]
       }
     }
   }

---

{
     "name": "parents",
     "preset": "private_chat",
     "is_direct": false,
     "initial_state": [
       { "type": "m.room.encryption", "state_key": "", "content": { "algorithm": "m.megolm.v1.aes-sha2" } }
     ],
     "invite": ["@user:server"]
   }

---

const memberStateFlag = await hasDirectMatrixMemberFlag(params.client, params.roomId, selfUserId);
// Explicit local override: is_direct === false negates the strict-DM classification
if (memberStateFlag === false) {
  return {
    joinedMembers,
    strict: false,
    viaMemberState: false,
    memberStateFlag,
  };
}
RAW_BUFFERClick to expand / collapse

Summary

extensions/matrix/src/matrix/direct-room.ts classifies any 2-member room (bot + 1 user) as a "strict direct room" using a pure member-count heuristic. This happens before per-room/per-group config is consulted, so:

  • A room explicitly configured under channels.matrix.groups[<roomId>] with requireMention: true is still treated as a DM at runtime.
  • A room created with is_direct: false and a name is still treated as a DM.
  • DM mode bypasses requireMention, so the bot responds to every message in those rooms regardless of mention status.

The CHANGELOG entry from #57124 ("honor explicit local is_direct: false for discovered DM candidates") was a partial fix — only for inherited/discovered DM candidates, not for the runtime isStrictDirectRoom path that drives mention/skill/session routing.

Affected code

// extensions/matrix/src/matrix/direct-room.ts
export function isStrictDirectMembership(params: {
  selfUserId?: string | null;
  remoteUserId?: string | null;
  joinedMembers?: readonly string[] | null;
}): boolean {
  // ...
  return Boolean(
    selfUserId &&
    remoteUserId &&
    joinedMembers.length === 2 &&        // ← pure heuristic
    joinedMembers.includes(selfUserId) &&
    joinedMembers.includes(remoteUserId),
  );
}

inspectMatrixDirectRoomEvidence does read the is_direct membership flag via hasDirectMatrixMemberFlag, but the result (memberStateFlag) is never used to negate the strict flag — even when explicitly false. isStrictDirectRoom returns .strict directly without considering is_direct: false or local config.

Reproduction (verified on 2026.5.19)

  1. Configure OpenClaw with:

    "channels.matrix": {
      "enabled": true,
      "encryption": true,
      "dm": {
        "enabled": true,
        "policy": "allowlist",
        "allowFrom": ["@user:server"],
        "sessionScope": "per-room"
      },
      "groupPolicy": "open",
      "groups": {
        "!roomId:server": {
          "requireMention": true,
          "skills": ["..."]
        }
      }
    }
  2. Create a Matrix room via the client API with:

    {
      "name": "parents",
      "preset": "private_chat",
      "is_direct": false,
      "initial_state": [
        { "type": "m.room.encryption", "state_key": "", "content": { "algorithm": "m.megolm.v1.aes-sha2" } }
      ],
      "invite": ["@user:server"]
    }
  3. Invite the bot, bot joins → room has 2 members. The room has a name, is_direct: false on the membership events, and is explicitly in OpenClaw's groups[] map.

  4. From @user:server, send a plain message (no @bot mention) → bot responds despite requireMention: true.

  5. Add a third member (any user) to the same room → restart not required, eventually joinedMembers.length === 3 so isStrictDirectMembership returns falserequireMention: true is now honored: plain messages ignored, only @bot ... triggers a reply.

The workaround "add a third member" confirms the diagnosis: the 2-person count is the sole DM classifier.

Why this matters

The pattern "private 1:1 room with the bot that should still require mentions" is reasonable: a personal scratch room for an admin, a held-aside room before adding family/team members, an OPS room where the user wants explicit invocation. The current code makes this impossible without inviting a placeholder third user.

The deeper issue: groups[<roomId>] config has no effect for any 2-person room. The schema's rooms vs groups distinction is silently broken for the smallest rooms.

Proposed fix shape

In isStrictDirectRoom (or its callers), treat as NOT a strict direct room when any of the following hold:

  1. The bot's own m.room.member event has is_direct: false (already readable via hasDirectMatrixMemberFlag — just needs to be honored as a veto, not an augmentation).
  2. The room's room_id is explicitly listed in channels.matrix.groups[].
  3. (Optional, more conservative) The room has an m.room.name set — DMs are conventionally nameless.

Suggested minimal patch in inspectMatrixDirectRoomEvidence:

const memberStateFlag = await hasDirectMatrixMemberFlag(params.client, params.roomId, selfUserId);
// Explicit local override: is_direct === false negates the strict-DM classification
if (memberStateFlag === false) {
  return {
    joinedMembers,
    strict: false,
    viaMemberState: false,
    memberStateFlag,
  };
}

Then a separate change in the channel runtime to check groups[roomId] presence before routing through DM logic.

Environment

  • OpenClaw: 2026.5.19
  • Matrix homeserver: Synapse (latest)
  • Matrix plugin: bundled @openclaw/matrix
  • Node: as shipped in ghcr.io/openclaw/openclaw:latest

Related

  • #57124 — partial fix for the same root cause, addressing "discovered DM candidates" only.
  • #64785 — closed, related but distinct (mention detection on non-OpenClaw senders, after the room is correctly classified as a group).

Happy to draft a PR if the maintainers concur on the fix shape — wanted to file the issue first to get the design discussion in the right place.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING