Matrix Channel Plugin: Setup and Configuration

Learn how to install and configure the Matrix channel plugin for OpenClaw, covering setup, authentication, and auto-join. Essential for developers integrating Matrix messaging.

Read this when

  • Setting up Matrix in OpenClaw
  • Configuring Matrix E2EE and verification

Matrix is a downloadable channel plugin (@openclaw/matrix) that wraps the official matrix-js-sdk. It handles DMs, rooms, threads, media, reactions, polls, location data, and E2EE.

Install

openclaw plugins install @openclaw/matrix

When only bare plugin specs are given, ClawHub is checked first, with npm as the fallback. You can force a particular source through openclaw plugins install clawhub:@openclaw/matrix or npm:@openclaw/matrix. For a local checkout, use openclaw plugins install ./path/to/local/matrix-plugin.

The plugin gets registered and enabled via plugins install; a separate enable step isn't required. Until you configure it as described below, the channel remains inactive. General installation guidance lives in Plugins.

Setup

  1. Set up a Matrix account on your homeserver.
  2. Fill in channels.matrix using either homeserver plus accessToken, or homeserver combined with userId and password.
  3. Restart the gateway.
  4. Send a DM to the bot, or add it to a room. New invites are only accepted when autoJoin permits them.

Interactive setup

openclaw channels add
openclaw configure --section channels

The wizard collects the homeserver URL, an auth method (token or password), a user ID (only for password auth), an optional device name, an E2EE toggle, and room access or auto-join preferences. If MATRIX_* env vars are already present and no saved auth exists for the account, a shortcut using those env vars is offered. Resolve room names before you save an allowlist with openclaw channels resolve --channel matrix "Project Room". Turning on E2EE in the wizard performs the same bootstrap as openclaw matrix encryption setup.

Minimal config

Token-based:

{
  channels: {
    matrix: {
      enabled: true,
      homeserver: "https://matrix.example.org",
      accessToken: "syt_xxx",
      dm: { policy: "pairing" },
    },
  },
}

Password-based (the token gets cached after the first login):

{
  channels: {
    matrix: {
      enabled: true,
      homeserver: "https://matrix.example.org",
      userId: "@bot:example.org",
      password: "replace-me", // pragma: allowlist secret
      deviceName: "OpenClaw Gateway",
    },
  },
}

SecretRefs for tokens and passwords follow the shared source-specific provider-alias rules, including for named accounts. An explicit matching env provider still applies its allowlist; an empty allowlist blocks all variables.

Auto-join

channels.matrix.autoJoin is set to "off" by default, meaning the bot won't show up in new rooms or DMs from fresh invites until you join manually. OpenClaw can't distinguish a DM from a group at invite time, so every invite is routed through autoJoin first; dm.policy only takes effect later, after the bot has joined and the room type is determined.

Warning

Use autoJoin: "allowlist" together with autoJoinAllowlist to limit which invites are accepted, or autoJoin: "always" to accept all invites.

autoJoinAllowlist accepts only a literal room ID (!roomId:server, or the suffixless !roomId variant from room version 12 and newer), #alias:server, or *. Plain room names are not accepted; aliases are resolved against the homeserver, not against state the invited room claims.

{
  channels: {
    matrix: {
      autoJoin: "allowlist",
      autoJoinAllowlist: ["!ops:example.org", "#support:example.org"],
      groups: {
        "!ops:example.org": { requireMention: true },
      },
    },
  },
}

Group join introductions

When the bot joins an allowed group room, it posts a single introduction based on the room name, topic, and up to 100 readable recent room messages. If history can't be read, the introduction relies only on available metadata and doesn't fabricate room activity.

Introductions are on by default. To turn them off, set channels.matrix.joinIntro: false, or use channels.matrix.accounts.<accountId>.joinIntro to override for one account. Direct rooms never get introductions. Only an actual join transition triggers one: an unaccepted invite, a startup snapshot of an existing room, or a profile update while already joined won't. This leaves autoJoin unchanged, which defaults to "off".

For room admission, once-per-room behavior, and the no-tools turn that treats room content as untrusted, see group join introductions.

Allowlist target formats

Matrix user IDs are case-sensitive. Copy the exact @user:server value Matrix reports for every allowlist, approver, and approval-target field. If an existing config used different casing, update it manually; OpenClaw cannot safely infer or rewrite the intended account because case-distinct IDs can identify different users.

  • Direct messages (dm.allowFrom, groupAllowFrom, groups.<room>.users): rely on @user:server. By default, display names are ignored since they can change; only set dangerouslyAllowNameMatching: true when you explicitly need display-name compatibility.
  • Approval forwarding (approvals.exec.targets[].to combined with channel: "matrix"): use user:@user:server, matching Matrix casing exactly.
  • Room allowlist keys (groups, plus the older alias rooms): go with !room:server (or the suffixless !room variant on room version 12 and newer) or #alias:server. Unless dangerouslyAllowNameMatching: true is set, plain names are not considered.
  • Invite allowlists (autoJoinAllowlist): choose !room:server (or suffixless !room on room version 12+), #alias:server, or *. Plain names are rejected outright.

Account ID normalization

The wizard turns a friendly name into a normalized account ID (Ops Bot becomes ops-bot). To prevent account collisions, punctuation in scoped environment variable names is hex-escaped: - (0x2D) is transformed into _X2D_, so ops-prod corresponds to the env prefix MATRIX_OPS_X2D_PROD_.

Cached credentials

Account credentials for Matrix are cached in the shared state/openclaw.sqlite plugin state. When those cached credentials are present, OpenClaw considers Matrix configured even if the config file lacks an accessToken, covering setup, openclaw doctor, and channel-status checks. During upgrades, the deprecated ~/.openclaw/credentials/matrix/credentials*.json files are imported via openclaw doctor --fix, the SQLite rows are verified, and then the files are archived.

Environment variables

Environment variables backed by config keys, applied when the matching config key is not set. The default account uses names without prefixes; for named accounts, the account token is inserted before the suffix (see normalization).

Default accountNamed account (<ID> = account token)
MATRIX_HOMESERVERMATRIX_<ID>_HOMESERVER
MATRIX_ACCESS_TOKENMATRIX_<ID>_ACCESS_TOKEN
MATRIX_USER_IDMATRIX_<ID>_USER_ID
MATRIX_PASSWORDMATRIX_<ID>_PASSWORD
MATRIX_DEVICE_IDMATRIX_<ID>_DEVICE_ID
MATRIX_DEVICE_NAMEMATRIX_<ID>_DEVICE_NAME

For account ops, the names become MATRIX_OPS_HOMESERVER, MATRIX_OPS_ACCESS_TOKEN, and so forth. MATRIX_HOMESERVER (including any *_HOMESERVER scoped variant) cannot be assigned from a workspace .env; refer to Workspace .env files.

Note

The recovery key is not a config-backed environment variable: OpenClaw never reads it from the environment on its own. CLI guidance text recommends piping it through a shell variable called MATRIX_RECOVERY_KEY for the default account, or MATRIX_RECOVERY_KEY_<ID> (plain uppercased account ID, without hex-escaping) for a named account, as described in Verify this device with a recovery key.

Configuration example

A practical starting point that includes DM pairing, a room allowlist, and E2EE:

{
  channels: {
    matrix: {
      enabled: true,
      homeserver: "https://matrix.example.org",
      accessToken: "syt_xxx",
      encryption: true,

      dm: {
        policy: "pairing",
        sessionScope: "per-room",
        threadReplies: "off",
      },

      groupPolicy: "allowlist",
      groupAllowFrom: ["@admin:example.org"],
      groups: {
        "!roomid:example.org": { requireMention: true },
      },

      autoJoin: "allowlist",
      autoJoinAllowlist: ["!roomid:example.org"],
      threadReplies: "inbound",
      replyToMode: "off",
      streaming: { mode: "partial" },
    },
  },
}

Streaming previews

Matrix reply streaming requires explicit activation. streaming.mode determines how OpenClaw delivers the assistant reply while it is being generated; streaming.block.enabled decides whether each finished block remains a separate Matrix message.

{
  channels: {
    matrix: {
      streaming: { mode: "partial" },
    },
  },
}

For live answer previews without visible tool or progress lines:

{
  channels: {
    matrix: {
      streaming: {
        mode: "partial",
        preview: {
          toolProgress: false,
        },
      },
    },
  },
}

The complete configuration accepts { mode, chunkMode, block, preview, progress }:

{
  channels: {
    matrix: {
      streaming: {
        mode: "progress",
        progress: {
          label: "auto", // pick from configured or built-in labels (false to hide)
          labels: ["Thinking", "Writing", "Searching"], // candidates for label: "auto"
          maxLines: 8, // max rolling progress lines (default: 8)
          maxLineChars: 120, // max chars per line before truncation (default: 120)
          toolProgress: true, // show tool/progress activity (default: true)
        },
      },
    },
  },
}
  • progress.label: a custom label, "auto" or unset to use a configured or built-in label, or false to suppress it.
  • progress.labels: candidates apply only when label equals "auto" or is unset.
  • progress.maxLines: the maximum number of rolling progress lines held in the draft; anything older gets trimmed beyond this point.
  • progress.maxLineChars: the character cap for each compact progress line before truncation kicks in.
  • progress.toolProgress: when set to true (the default), live tool and progress activity shows up in the draft.
streaming.modeBehavior
"off" (default)Hold the complete reply, then send it in one go.
"partial"Modify a single normal text message in place as the model writes the current block. Stock clients may alert on the first preview rather than the final edit.
"quiet"Identical to "partial" except the message is a notice that does not notify. Recipients get notified only when a per-user push rule matches the finalized edit (details below).
"progress"Emits separate compact progress lines through a progress draft.

streaming.block.enabled (default false) operates independently of streaming.mode:

streaming.modeblock.enabled: trueblock.enabled: false (default)
"partial" / "quiet"Draft the current block live, keep finished blocks as messagesDraft the current block live, finalize it in place
"off"One notifying Matrix message per completed blockOne notifying Matrix message for the whole reply

Notes:

  • If a preview exceeds Matrix's per-event size limit, OpenClaw halts preview streaming and switches to final-only delivery.
  • Media replies always send attachments the normal way. When a visible preview cannot be safely reused, OpenClaw holds it until the full replacement is confirmed, then redacts it. If replacement delivery fails, arrives incomplete, or produces no visible event, the preview stays up.
  • Tool-progress preview updates are enabled by default whenever preview streaming is active. Set streaming.preview.toolProgress: false to keep preview edits for answer text while routing tool progress through the standard delivery path.
  • Preview edits consume additional Matrix API calls. Leave streaming.mode: "off" for the most conservative rate-limit behavior.
  • Legacy scalar or boolean streaming values, along with the flat blockStreaming and chunkMode keys, get converted into this nested structure by openclaw doctor --fix.

Voice messages

Inbound Matrix voice notes get transcribed before the room mention gate, so a voice note that says the bot name can trigger the agent in a requireMention: true room, and the agent receives the transcript instead of only an audio attachment placeholder.

Matrix relies on the shared audio media provider under tools.media.audio, such as OpenAI gpt-4o-mini-transcribe. See Media tools overview for provider setup and limits.

  • m.audio events and m.file events carrying an audio/* MIME type qualify.
  • In encrypted rooms, OpenClaw decrypts the attachment through the existing Matrix media path before transcription.
  • The transcript is flagged as machine-generated and untrusted in the agent prompt.
  • The attachment is marked as already transcribed so downstream media tools skip it.
  • Set tools.media.audio.enabled: false to turn off audio transcription globally.

Approval metadata

Matrix native approval prompts are ordinary m.room.message events with OpenClaw-specific content under the com.openclaw.approval key. Stock clients still render the text body; OpenClaw-aware clients can read the structured approval id, kind, state, decisions, and exec or plugin details.

When a prompt is too long for a single Matrix event, OpenClaw splits the visible text into chunks and attaches com.openclaw.approval to the first chunk only. Allow and deny reactions bind to that first event, so long prompts keep the same approval target as single-event prompts.

Self-hosted push rules for quiet finalized previews

streaming.mode: "quiet" alerts recipients only after a block or turn is finalized; a per-user push rule must match the finalized preview marker. See Matrix push rules for quiet previews for the complete recipe.

Bot-to-bot rooms

By default, Matrix messages from other configured OpenClaw Matrix accounts are ignored. Use allowBots to deliberately allow inter-agent traffic:

{
  channels: {
    matrix: {
      allowBots: "mentions", // true | "mentions"
      groups: {
        "!roomid:example.org": {
          requireMention: true,
        },
      },
    },
  },
}
  • allowBots: true pulls in messages sent by other configured Matrix bot accounts, whether they appear in allowed rooms or in direct messages.
  • allowBots: "mentions" only accepts those messages when the bot is explicitly mentioned in a room; DMs are exempt from this requirement.
  • groups.<room>.allowBots lets you override the account-wide setting for a single room.
  • Messages from configured bots share the same bot loop protection. Set channels.defaults.botLoopProtection, then adjust it per account with channels.matrix.botLoopProtection or per room with channels.matrix.groups.<room>.botLoopProtection.
  • To prevent self-reply loops, OpenClaw still disregards messages originating from the same Matrix user ID.
  • Since Matrix lacks a native bot flag, OpenClaw defines "bot-authored" as "sent by another configured Matrix account on this OpenClaw gateway".

When enabling bot-to-bot communication in shared rooms, enforce strict room allowlists and mention requirements.

Encryption and verification

In encrypted (E2EE) rooms, outbound image events rely on thumbnail_file so that image previews are encrypted with the full attachment; unencrypted rooms fall back to plain thumbnail_url. No setup is required, as the plugin automatically detects E2EE state.

All openclaw matrix commands support --verbose (full diagnostics), --json (machine-readable output), and --account <id> (multi-account setups). Output stays concise by default.

Enable encryption

openclaw matrix encryption setup
printf '%s\n' "$MATRIX_RECOVERY_KEY" | openclaw matrix encryption setup --recovery-key-stdin

This bootstraps secret storage and cross-signing, sets up a room-key backup if necessary, and then reports status and next steps. Useful flags:

  • --recovery-key-stdin pulls a recovery key from stdin without exposing it in process arguments; --recovery-key <key> remains for compatibility
  • --force-reset-cross-signing discards the current cross-signing identity and generates a new one (for intentional use only)

For a new account, enable E2EE at creation time:

openclaw matrix account add \
  --homeserver https://matrix.example.org \
  --access-token syt_xxx \
  --enable-e2ee

--encryption serves as an alias for --enable-e2ee. Manual config equivalent:

{
  channels: {
    matrix: {
      enabled: true,
      homeserver: "https://matrix.example.org",
      accessToken: "syt_xxx",
      encryption: true,
      dm: { policy: "pairing" },
    },
  },
}

Status and trust signals

openclaw matrix verify status
openclaw matrix verify status --include-recovery-key --json

With --include-recovery-key, text output confirms when a raw recovery key is available and points you to add --json. The key itself never appears in text output; keep any JSON output containing a recovery key private.

verify status reports three independent trust signals (--verbose shows all of them):

  • Locally trusted: trusted only by this client
  • Cross-signing verified: the SDK indicates verification via cross-signing
  • Signed by owner: signed by your own self-signing key (diagnostic only)

Verified by owner is yes only when Cross-signing verified is yes; local trust or an owner signature alone is insufficient.

--allow-degraded-local-state returns best-effort diagnostics without first preparing the Matrix account; useful for offline or partially-configured probes.

Verify this device with a recovery key

Pipe the recovery key via stdin instead of passing it on the command line:

printf '%s\n' "$MATRIX_RECOVERY_KEY" | openclaw matrix verify device --recovery-key-stdin

The command reports three states:

  • Recovery key accepted: Matrix accepted the key for secret storage or device trust.
  • Backup usable: room-key backup can be loaded with the trusted recovery material.
  • Device verified by owner: this device has full Matrix cross-signing identity trust.

It exits non-zero when full identity trust is incomplete, even if the recovery key unlocked backup material. In that case, finish self-verification from another Matrix client:

openclaw matrix verify self

verify self waits for Cross-signing verified: yes before exiting successfully. Use --timeout-ms <ms> to tune the wait.

The literal-key form openclaw matrix verify device "<recovery-key>" also works, but the key ends up in shell history.

Bootstrap or repair cross-signing

openclaw matrix verify bootstrap

The repair/setup command for encrypted accounts. In order, it:

  • bootstraps secret storage, reusing an existing recovery key when possible
  • bootstraps cross-signing and uploads missing public keys
  • marks and cross-signs the current device
  • creates a server-side room-key backup if one does not already exist

If the homeserver requires UIA to upload cross-signing keys, OpenClaw tries no-auth first, then m.login.dummy, then m.login.password (requires channels.matrix.password).

Useful flags:

  • --recovery-key-stdin (pair with printf '%s\n' "$MATRIX_RECOVERY_KEY" | ...) or --recovery-key <key>
  • --force-reset-cross-signing to discard the current cross-signing identity (intentional only; requires the active recovery key stored or supplied with --recovery-key-stdin)

Room-key backup

openclaw matrix verify backup status
printf '%s\n' "$MATRIX_RECOVERY_KEY" | openclaw matrix verify backup restore --recovery-key-stdin

backup status indicates whether a server-side backup is present and if this device has the ability to decrypt it. To bring backed-up room keys into the local crypto store, use backup restore; drop --recovery-key-stdin when the recovery key is already saved to disk.

When you need to swap a damaged backup for a clean starting point (this accepts the loss of unrecoverable old history; it can also rebuild secret storage if the current backup secret cannot be loaded):

openclaw matrix verify backup reset --yes

Include --rotate-recovery-key only if the earlier recovery key should deliberately no longer unlock the new backup baseline.

Listing, requesting, and responding to verifications

openclaw matrix verify list

Shows the verification requests that are waiting for the selected account.

openclaw matrix verify request --own-user
openclaw matrix verify request --user-id @ops:example.org --device-id ABCDEF

Sends a verification request from this account. Self-verification is requested via --own-user (confirm the prompt in another Matrix client belonging to the same user); --user-id/--device-id/--room-id are for targeting a different person. The other targeting flags cannot be used together with --own-user.

For lower-level lifecycle operations, typically used while shadowing inbound requests from another client, these commands act on a specific request <id> (which is shown by verify list and verify request):

CommandPurpose
openclaw matrix verify accept <id>Accept an inbound request
openclaw matrix verify start <id>Start the SAS flow
openclaw matrix verify sas <id>Print the SAS emoji or decimals
openclaw matrix verify confirm-sas <id>Confirm that the SAS matches what the other client shows
openclaw matrix verify mismatch-sas <id>Reject the SAS when the emoji or decimals do not match
openclaw matrix verify cancel <id>Cancel; takes optional --reason <text> and --code <matrix-code>

When verification is tied to a specific direct-message room, accept, start, sas, confirm-sas, mismatch-sas, and cancel all accept --user-id and --room-id as DM follow-up hints.

Multi-account notes

Matrix CLI commands rely on the implicit default account unless --account <id> is provided. If multiple named accounts exist and channels.matrix.defaultAccount is missing, commands refuse to guess and ask for a choice. When E2EE is turned off or unavailable for a named account, errors point to that account's config key, for example channels.matrix.accounts.assistant.encryption.

Startup behavior

With encryption: true, startupVerification falls back to "if-unverified". At startup, a device that is not verified requests self-verification in another Matrix client, skipping duplicates and applying a cooldown (24 hours by default). Adjust this with startupVerificationCooldownHours or turn it off with startupVerification: "off".

A conservative crypto bootstrap pass also runs at startup, reusing the existing secret storage and cross-signing identity. If bootstrap state is corrupted, OpenClaw attempts a guarded repair even without channels.matrix.password; when the homeserver demands password UIA, startup logs a warning and remains non-fatal. Devices already signed by the owner are kept intact.

The complete upgrade flow is described in Matrix migration.

Verification notices

Lifecycle notices for Matrix verification are posted into the strict DM verification room as m.notice messages: request, ready (with "Verify by emoji" guidance), start/completion, and SAS (emoji/decimal) details when they are available.

Incoming requests from another Matrix client are tracked and auto-accepted. For self-verification, OpenClaw automatically starts the SAS flow and confirms its own side once emoji verification is available; you still need to compare and confirm "They match" in your Matrix client.

Verification system notices are not sent to the agent chat pipeline.

Deleted or invalid Matrix device

If verify status reports that the current device is no longer on the homeserver, set up a new OpenClaw Matrix device. For password login:

openclaw matrix account add \
  --account assistant \
  --homeserver https://matrix.example.org \
  --user-id '@assistant:example.org' \
  --password '<password>' \
  --device-name OpenClaw-Gateway

For token auth, generate a fresh access token in your Matrix client or admin UI, then update OpenClaw:

openclaw matrix account add \
  --account assistant \
  --homeserver https://matrix.example.org \
  --access-token '<token>'

Swap assistant for the account ID from the failed command, or leave out --account for the default account.

Device hygiene

Old OpenClaw-managed devices can pile up. List and remove them:

openclaw matrix devices list
openclaw matrix devices prune-stale

Crypto store

Matrix E2EE relies on the official matrix-js-sdk Rust crypto path, with fake-indexeddb serving as the IndexedDB shim. Crypto state is saved to crypto-idb-snapshot.json (with restrictive file permissions).

Encrypted runtime state is stored under ~/.openclaw/matrix/accounts/<account>/<homeserver>__<user>/<token-hash>/ and covers the sync store, crypto store, recovery key, IDB snapshot, thread bindings, and startup verification state. When the token changes but the account identity remains the same, OpenClaw reuses the best existing root so prior state stays visible.

A single older token-hash root can serve as a normal token-rotation continuity path. When OpenClaw logs matrix: multiple populated token-hash storage roots detected, check the account directory and move stale sibling roots into an _archive/ directory only after verifying the selected active root is healthy, rather than deleting them right away.

Profile management

openclaw matrix profile set --name "OpenClaw Assistant"
openclaw matrix profile set --avatar-url https://cdn.example.org/avatar.png

Pass both options in a single call. Matrix accepts mxc:// avatar URLs directly; using http:///https:// uploads the file first, then stores the resolved mxc:// URL into channels.matrix.avatarUrl (or the per-account override).

Threads

Matrix supports native threads for both automatic replies and message-tool sends. Two independent knobs control behavior:

Session routing (sessionScope)

dm.sessionScope determines how Matrix DM rooms map to OpenClaw sessions:

  • "per-user" (default): all DM rooms with the same routed peer share one session.
  • "per-room": each Matrix DM room gets its own session key, even for the same peer.

Explicit conversation bindings always win over sessionScope; bound rooms and threads keep their chosen target session.

Reply threading (threadReplies)

threadReplies decides where the bot posts its reply:

  • "off": replies are top-level. Inbound threaded messages stay on the parent session.
  • "inbound": reply inside a thread only when the inbound message was already in that thread.
  • "always": reply inside a thread rooted at the triggering message; that conversation routes through a matching thread-scoped session from the first trigger onward.

dm.threadReplies overrides this for DMs only - for example, keep room threads isolated while keeping DMs flat.

Thread inheritance and slash commands

  • Inbound threaded messages include the thread root message as extra agent context.
  • Message-tool sends auto-inherit the current Matrix thread when targeting the same room (or the same DM user target), unless an explicit threadId is provided.
  • DM user-target reuse only kicks in when current session metadata proves the same DM peer on the same Matrix account; otherwise OpenClaw falls back to normal user-scoped routing.
  • /focus, /unfocus, /agents, /session idle, /session max-age, and thread-bound /acp spawn all work in Matrix rooms and DMs.
  • Top-level /focus creates a new Matrix thread and binds it to the target session when threadBindings.spawnSessions is enabled.
  • Running /focus or /acp spawn --thread here inside an existing Matrix thread binds that thread in place.

When OpenClaw detects a Matrix DM room colliding with another DM room on the same shared session, it posts a one-time m.notice pointing to the /focus escape hatch and suggesting a dm.sessionScope change. The notice only appears when thread bindings are enabled.

ACP conversation bindings

Matrix rooms, DMs, and existing Matrix threads can become durable ACP workspaces without changing the chat surface.

Fast operator flow:

  • Run /acp spawn codex --bind here inside the Matrix DM, room, or existing thread to keep using.
  • In a top-level DM or room, the current DM/room stays the chat surface and future messages route to the spawned ACP session.
  • Inside an existing thread, --bind here binds that current thread in place.
  • /new and /reset reset the same bound ACP session in place.
  • /acp close closes the ACP session and removes the binding.

--bind here does not create a child Matrix thread. threadBindings.spawnSessions gates /acp spawn --thread auto|here, where OpenClaw needs to create or bind a child thread.

Thread binding config

Matrix inherits global defaults from session.threadBindings and supports per-channel overrides:

  • threadBindings.enabled
  • threadBindings.idleHours
  • threadBindings.maxAgeHours
  • threadBindings.spawnSessions: gates both subagent and ACP thread spawns.
  • Deprecated threadBindings.spawnSubagentSessions / threadBindings.spawnAcpSessions keys are migrated to spawnSessions by openclaw doctor --fix.
  • threadBindings.defaultSpawnContext

Matrix thread-bound session spawns default on. Set threadBindings.spawnSessions: false to block top-level /focus and /acp spawn --thread auto|here from creating/binding Matrix threads. Set threadBindings.defaultSpawnContext: "isolated" when native subagent thread spawns should not fork the parent transcript.

Reactions

Reactions sent outbound, received as notifications, and acknowledged are all supported by Matrix.

Access to the outbound reaction tooling depends on channels.matrix.actions.reactions:

  • A reaction can be attached to a Matrix event with react.
  • reactions retrieves the reaction summary currently associated with a Matrix event.
  • Custom emoji are discovered via emoji-list from the room packs in the active conversation and from your personal pack.
  • The bot's own reactions on that event are cleared by emoji="".
  • Only the designated emoji reaction is removed from the bot using remove: true.

emoji-list reads MSC2545 im.ponies.room_emotes packs from the authorized current room and im.ponies.user_emotes account data. It returns up to 100 sorted entries like { "name": "party", "identifier": "party", "url": "mxc://example.org/party" }; sticker-only entries are filtered out. Pass identifier to react: it is the plain shortcode stored directly as the Matrix reaction's m.relates_to.key, not the mxc:// media URL. Rendering custom reactions depends on the Matrix client, so url is included separately for clients or agents that need the image.

Resolution order (first defined value wins):

SettingOrder
ackReactionper-account -> channel -> messages.ackReaction -> agent identity emoji fallback
ackReactionScopeper-account -> channel -> messages.ackReactionScope -> default "group-mentions"
reactionNotificationsper-account -> channel -> default "own"

reactionNotifications: "own" forwards added m.reaction events when they target bot-authored Matrix messages; "off" disables reaction system events. Reaction removals are not synthesized into system events - Matrix surfaces those as redactions, not as standalone m.reaction removals.

History context

  • channels.matrix.historyLimit sets how many recent room messages are included as InboundHistory when a room message triggers the agent. Falls back to messages.groupChat.historyLimit; effective default 0 if both are unset (disabled).
  • Matrix room history is room-only; DMs keep using normal session history.
  • Room history is pending-only: OpenClaw buffers room messages that did not trigger a reply yet, then snapshots that window when a mention or other trigger arrives.
  • The current trigger message is not included in InboundHistory; it stays in the main inbound body for that turn.
  • Retries of the same Matrix event reuse the original history snapshot instead of drifting forward to newer room messages.

Context visibility

Matrix supports the shared contextVisibility control for supplemental room context such as fetched reply text, thread roots, and pending history.

  • contextVisibility: "all" is the default. Supplemental context is kept as received.
  • contextVisibility: "allowlist" filters supplemental context to senders allowed by the active room/user allowlist checks.
  • contextVisibility: "allowlist_quote" behaves like allowlist, but still keeps one explicit quoted reply.

This affects supplemental context visibility only, not whether the inbound message itself can trigger a reply. Trigger authorization still comes from groupPolicy, groups, groupAllowFrom, and DM policy settings.

DM and room policy

{
  channels: {
    matrix: {
      dm: {
        policy: "allowlist",
        allowFrom: ["@admin:example.org"],
        threadReplies: "off",
      },
      groupPolicy: "allowlist",
      groupAllowFrom: ["@admin:example.org"],
      groups: {
        "!roomid:example.org": { requireMention: true },
      },
    },
  },
}

To silence DMs entirely while keeping rooms working, set dm.enabled: false:

{
  channels: {
    matrix: {
      dm: { enabled: false },
      groupPolicy: "allowlist",
      groupAllowFrom: ["@admin:example.org"],
    },
  },
}

See Groups for mention-gating and allowlist behavior.

Pairing example for Matrix DMs:

openclaw pairing list matrix
openclaw pairing approve matrix <CODE>

If an unapproved Matrix user keeps messaging before approval, OpenClaw reuses the same pending pairing code and may send a reminder reply after a short cooldown instead of minting a new code.

See Pairing for the shared DM pairing flow and storage layout.

Direct room repair

If direct-message state drifts, OpenClaw can end up with stale m.direct mappings pointing at old solo rooms instead of the live DM. Inspect the current mapping for a peer:

openclaw matrix direct inspect --user-id @alice:example.org

Repair it:

openclaw matrix direct repair --user-id @alice:example.org

Both commands accept --account <id> for multi-account setups. The repair flow:

  • prefers a strict 1:1 DM already mapped in m.direct
  • falls back to any currently joined strict 1:1 DM with that user
  • creates a fresh direct room and rewrites m.direct if no healthy DM exists

It does not delete old rooms automatically. It picks the healthy DM and updates the mapping so future Matrix sends, verification notices, and other direct-message flows target the right room.

Exec approvals

Matrix can act as a native approval client. Configure under channels.matrix.execApprovals (or channels.matrix.accounts.<account>.execApprovals for a per-account override):

  • enabled: route approvals through Matrix-native prompts. When unset, or set to "auto", this activates automatically once at least one approver can be identified; use false to turn it off explicitly.
  • approvers: Matrix user IDs (@owner:example.org) permitted to approve exec requests. If not set, it defaults to channels.matrix.dm.allowFrom.
  • target: determines where prompts are delivered. "dm" (the default) routes to approver DMs; "channel" routes to the source room or DM; "both" routes to both.
  • agentFilter / sessionFilter: optional allowlists controlling which agents/sessions trigger Matrix delivery.

Authorization varies by approval type:

  • Exec approvals rely on execApprovals.approvers, with dm.allowFrom as the fallback.
  • Plugin approvals are authorized exclusively through dm.allowFrom.

Both types share Matrix reaction shortcuts and message updates. Approvers see reaction shortcuts on the primary approval message:

  • ✅ allow once
  • ❌ deny
  • ♾️ allow always (when the effective exec policy permits it)

Fallback slash commands: /approve <id> allow-once, /approve <id> allow-always, /approve <id> deny.

Only resolved approvers can approve or deny. Exec approval channel delivery includes the command text; only enable channel or both in trusted rooms.

Related: Exec approvals.

Slash commands

Slash commands (/new, /reset, /model, /focus, /unfocus, /agents, /session, /acp, /approve, etc.) function directly in DMs. In rooms, OpenClaw also picks up commands prefixed with the bot's own Matrix mention, so @bot:server /new triggers the command path without a custom mention regex, keeping the bot responsive to the room-style @mention /command posts that Element and similar clients emit when a user tab-completes the bot before typing the command.

Authorization rules still apply: command senders must meet the same DM or room allowlist/owner policies as regular messages.

Multi-account

{
  channels: {
    matrix: {
      enabled: true,
      defaultAccount: "assistant",
      dm: { policy: "pairing" },
      accounts: {
        assistant: {
          homeserver: "https://matrix.example.org",
          accessToken: "syt_assistant_xxx",
          encryption: true,
        },
        alerts: {
          homeserver: "https://matrix.example.org",
          accessToken: "syt_alerts_xxx",
          dm: {
            policy: "allowlist",
            allowFrom: ["@ops:example.org"],
            threadReplies: "off",
          },
        },
      },
    },
  },
}

Inheritance:

  • Top-level channels.matrix values serve as defaults for named accounts unless an account overrides them.
  • Scope an inherited room entry to a specific account with groups.<room>.account. Entries without account are shared across accounts; account: "default" still works when the default account is configured at the top level.

Default account selection:

  • Set defaultAccount to choose the named account that implicit routing, probing, and CLI commands prefer.
  • If you have multiple accounts and one is literally named default, OpenClaw uses it implicitly even when defaultAccount is unset.
  • With multiple named accounts and no default selected, CLI commands refuse to guess; set defaultAccount or pass --account <id>.
  • The top-level channels.matrix.* block is only treated as the implicit default account when its auth is complete (homeserver + accessToken, or homeserver + userId + password). Named accounts remain discoverable from homeserver + userId once cached credentials cover auth.

Promotion:

  • When OpenClaw promotes a single-account config to multi-account during repair or setup, it preserves the existing named account if one exists or defaultAccount already points at one. Only Matrix auth/bootstrap keys move into the promoted account; shared delivery-policy keys stay at the top level.

See Configuration reference for the shared multi-account pattern.

Private/LAN homeservers

By default, OpenClaw blocks private/internal Matrix homeservers for SSRF protection unless you opt in per account.

If your homeserver runs on localhost, a LAN/Tailscale IP, or an internal hostname, enable network.dangerouslyAllowPrivateNetwork for that account:

{
  channels: {
    matrix: {
      homeserver: "http://matrix-synapse:8008",
      network: {
        dangerouslyAllowPrivateNetwork: true,
      },
      accessToken: "syt_internal_xxx",
    },
  },
}

CLI setup example:

openclaw matrix account add \
  --account ops \
  --homeserver http://matrix-synapse:8008 \
  --allow-private-network \
  --access-token syt_ops_xxx

This opt-in restricts access to trusted private or internal targets only. Public cleartext homeservers like http://matrix.example.org:8008 stay blocked. Whenever possible, prefer https://.

Proxying Matrix traffic

For a Matrix deployment that requires an explicit outbound HTTP(S) proxy, configure channels.matrix.proxy:

{
  channels: {
    matrix: {
      homeserver: "https://matrix.example.org",
      accessToken: "syt_bot_xxx",
      proxy: "http://127.0.0.1:7890",
    },
  },
}

Named accounts can replace the top-level default with channels.matrix.accounts.<id>.proxy. Both runtime Matrix traffic and account status probes from OpenClaw use this same proxy setting.

Target resolution

Wherever OpenClaw requests a room or user target, Matrix accepts these forms:

  • Users: @user:server, user:@user:server, or matrix:user:@user:server
  • Rooms: !room:server, room:!room:server, or matrix:room:!room:server (room IDs from version 12+ have no :server suffix: !room, room:!room, matrix:room:!room; they are handled identically)
  • Aliases: #alias:server, channel:#alias:server, or matrix:channel:#alias:server

Matrix room IDs distinguish case. When setting explicit delivery targets, cron jobs, bindings, or allowlists, match the exact casing of the room ID from Matrix. Internal session keys are kept canonical for storage by OpenClaw, so those lowercase keys should not be used to derive Matrix delivery IDs.

Live directory resolution relies on the currently logged-in Matrix account:

  • User lookups search the user directory of that homeserver.
  • Room lookups take explicit room IDs and aliases directly. Name-based lookup for joined rooms is best-effort, and applies only to runtime room allowlists when dangerouslyAllowNameMatching: true is set.
  • A room name that cannot be mapped to an ID or alias is skipped during runtime allowlist resolution.

Configuration reference

Allowlist-style user fields (groupAllowFrom, dm.allowFrom, groups.<room>.users) accept full Matrix user IDs, which is the safest option. Non-ID entries are skipped by default. When dangerouslyAllowNameMatching: true is set, exact display-name matches from the Matrix directory are resolved at startup and again whenever the allowlist changes while the monitor runs; entries that cannot be resolved are ignored at runtime.

Room allowlist keys (groups, legacy rooms) should hold room IDs or aliases. Plain room-name keys are skipped by default; dangerouslyAllowNameMatching: true re-enables best-effort matching against joined room names.

Account and connection

  • enabled: turns the channel on or off.
  • name: optional label shown for the account.
  • defaultAccount: preferred account ID when multiple Matrix accounts exist.
  • accounts: named per-account overrides. Top-level channels.matrix values serve as defaults.
  • homeserver: homeserver URL, such as https://matrix.example.org.
  • network.dangerouslyAllowPrivateNetwork: permits this account to reach localhost, LAN/Tailscale IPs, or internal hostnames.
  • proxy: optional HTTP(S) proxy URL for Matrix traffic. Can be overridden per account.
  • userId: full Matrix user ID (@bot:example.org).
  • accessToken: access token for token-based auth. Plaintext and SecretRef values work across env/file/exec/store providers (Secrets Management).
  • password: password for password-based login. Plaintext and SecretRef values supported.
  • deviceId: explicit Matrix device ID.
  • deviceName: device display name assigned at password-login time.
  • avatarUrl: stored self-avatar URL for profile sync and profile set updates.
  • initialSyncLimit: maximum events fetched during startup sync.

Encryption

  • encryption: enables E2EE. Default: false.
  • startupVerification: "if-unverified" (default when E2EE is active) or "off". If this device is unverified, self-verification is requested automatically at startup.
  • startupVerificationCooldownHours: cooldown before the next automatic startup request. Default: 24.

Access and policy

  • groupPolicy: accepts "open", "allowlist", or "disabled". Falls back to "allowlist" if not set.
  • groupAllowFrom: restricts room traffic to a defined set of user IDs.
  • mentionPatterns: defines room mention patterns using scoped regexes. An object holding { mode: "allow"|"deny", allowIn: [roomId, ...], denyIn: [roomId, ...] } determines whether the configured agents.entries.*.groupChat.mentionPatterns apply on a per-room basis.
  • dm.enabled: when set to false, all direct messages are skipped. The fallback is true.
  • dm.policy: options are "pairing" (the default), "allowlist", "open", or "disabled". This takes effect only after the bot has joined and identified the room as a DM; invite processing remains unaffected.
  • dm.allowFrom: limits DM traffic to a specified set of user IDs.
  • dm.sessionScope: either "per-user" (default) or "per-room".
  • dm.threadReplies: overrides reply threading for DMs only, using "off", "inbound", or "always".
  • allowBots: permits incoming messages from other configured Matrix bot accounts, via true or "mentions".
  • allowlistOnly: when true is active, all current DM policies, excluding "disabled", plus the "open" group policies are forced to "allowlist". The "disabled" policies stay unchanged.
  • dangerouslyAllowNameMatching: when true is set, directory lookup by display name works for user allowlist entries, and room-name lookup applies to room allowlist keys. Full @user:server IDs, room IDs, or aliases are preferred.
  • autoJoin: can be "always", "allowlist", or "off", with "off" as the default. This applies to every Matrix invite, including those styled as DMs.
  • autoJoinAllowlist: lists rooms or aliases permitted when autoJoin equals "allowlist". Alias entries resolve through the homeserver rather than against state reported by the invited room.
  • contextVisibility: controls extra context visibility, defaulting to "all", with "allowlist" and "allowlist_quote" as alternatives.

Reply behavior

  • joinIntro: triggered upon the bot's entry into an approved group room. Standard setting: true. Can be overridden per account: accounts.<accountId>.joinIntro.
  • replyToMode: options are "off" (the default), "first", "all", or "batched".
  • threadReplies: choose from "off" (unless explicitly configured, the top-level default points to "inbound"), "inbound", or "always".
  • threadBindings: per-channel adjustments governing thread-bound session routing and its lifecycle.
  • streaming: an embedded object { mode, chunkMode, block: { enabled, coalesce }, preview: { toolProgress }, progress: { label, labels, maxLines, maxLineChars, toolProgress } }. The value of mode can be "off" (default), "partial", "quiet", or "progress". Older scalar or boolean formats are converted via openclaw doctor --fix.
  • streaming.block.enabled: with true, finished assistant blocks remain as individual progress messages. Default: false.
  • markdown: optional Markdown rendering settings applied to outbound text.
  • responsePrefix: an optional string added to the start of outbound replies.
  • textChunkLimit: character-based chunk size for outbound traffic when streaming.chunkMode: "length" is active. Default: 4000.
  • streaming.chunkMode: either "length" (default, splits based on character count) or "newline" (breaks at line boundaries).
  • historyLimit: how many recent room messages are supplied as InboundHistory when the agent is activated by a room message. If not set, falls back to messages.groupChat.historyLimit; the effective default is 0 (turned off).
  • mediaMaxMb: maximum media size in MB for outbound sends and inbound processing. Default: 20.

Reaction settings

  • ackReaction: overrides the ack reaction for this channel or account.
  • ackReactionScope: scope override (default is "group-mentions", with alternatives "group-all", "direct", "all", "none", and "off").
  • reactionNotifications: how inbound reaction notifications are handled (default "own", or "off").

Tooling and per-room overrides

  • actions: tool access control applied per action (messages, reactions, pins, profile, memberInfo, channelInfo, verification).
  • groups: policy mapping based on individual rooms. After resolution, the stable room ID determines session identity. (rooms serves as a deprecated alternative.)
    • groups.<room>.account: limit a single inherited room entry to one designated account.
    • groups.<room>.enabled: room-specific switch. When false is active, the room is treated as absent from the map.
    • groups.<room>.requireMention: room-level adjustment of the mention prerequisite set at the channel scope.
    • groups.<room>.allowBots: room-level adjustment of the channel-wide option (true or "mentions").
    • groups.<room>.botLoopProtection: room-level adjustment for the budget guarding bot-to-bot loop prevention.
    • groups.<room>.users: room-specific list of permitted senders.
    • groups.<room>.tools: room-specific tool allow or deny exceptions.
    • groups.<room>.autoReply: room-level mention gating adjustment. true turns off mention requirements for that room; false reactivates them.
    • groups.<room>.skills: room-specific skill filtering.
    • groups.<room>.systemPrompt: room-specific system prompt fragment.

Exec approval settings

  • execApprovals.enabled: route exec approvals via Matrix-native prompts.
  • execApprovals.approvers: Matrix user IDs eligible to approve. If not set, dm.allowFrom is used.
  • execApprovals.target: "dm" (standard), "channel", or "both".
  • execApprovals.agentFilter / execApprovals.sessionFilter: optional agent or session allowlists for delivery.
6,782 words · updated Sep 1, 2026