RX Claude Code Matrix Bridge
Live two-way Matrix ↔ Claude Code TUI bridge via the Channels API research preview. Plugin + always-on daemon + per-session routing + headless fallback.
About
Sourced from arikw/claude-code-matrix-bridge README
Live two-way Matrix ↔ Claude Code TUI bridge via the Channels API research preview. Matrix messages appear inside your running Claude Code TUI as part of the conversation; Claude's replies post back to the room. Survives
claude --resume; falls back to headlessclaude --print --resumewhen the TUI is offline so messages never get lost.
Project page: https://arikw.github.io/claude-code-matrix-bridge/ — visual overview, feature tour, plugin comparison, screenshots.
| Status | v0.4.24 — works against Claude Code 2.1.143; channels API still research preview |
| Platforms | Linux + macOS. Windows works via WSL2 only — hooks + bin/* are bash scripts that won't run under cmd/PowerShell. |
| Requires | Claude Code ≥ v2.1.80 (Channels API) · Node.js ≥ 20 · Matrix homeserver + bot account |
| Shell deps | bash, jq, curl, python3, plus standard POSIX awk / sed / tr / cat |
| License | MIT |
| Encryption | Plaintext only (E2EE on roadmap) |
Install the shell deps once if they aren't already present:
# Debian / Ubuntu / WSL sudo apt-get install -y bash jq curl python3 # Alpine apk add bash jq curl python3 # macOS (Homebrew) brew install jq # bash, curl, python3 ship with macOS or via Xcode toolsNode.js: install whatever flow you prefer (nvm, fnm,
apt install nodejs, etc.) — must be ≥ 20 sodist/server.jsruns under the ESM bundle.
⚠ THIS BRIDGE REQUIRES A LAUNCH FLAG. Claude Code must be started with
--dangerously-load-development-channels plugin:rx-claude-matrix-bridge@arikw(see step 2). Without it, MCP tools work but matrix → TUI inbound is silently dropped. The bridge detects the missing flag and surfaces a⛓️💥glyph in the statusLine + a warning in/mx-link-chatoutput, but you'll still need to relaunch Claude Code.
How it compares
A few Matrix-side plugins for Claude Code exist. Different trade-offs:
| Plugin | Transport | Multi-session | Headless fallback | E2EE | Setup wizard |
|---|---|---|---|---|---|
| this project | Channels API push | ✅ built-in | ✅ auto | ❌ roadmap | ✅ |
| elkimek/matrix-bridge | MCP tool calls (pull) | ❌ | ❌ | ✅ (vodozemac) | ❌ manual |
| nazbav/claude-code-matrix-channel | Channels API push | ❌ | ❌ | ❌ unencrypted only | partial (skill) |
| Kholtien/claude-connect-matrix-integration | Channels API push | ❌ single-room | ❌ (systemd + tmux scaffold) | ✅ (Rust Olm) | ✅ |
What this plugin wins on: multi-session routing (N rooms ↔ N TUI sessions), headless fallback when the TUI is offline, the most complete setup wizard. What other plugins have that this one doesn't: end-to-end encryption (elkimek, Kholtien), permission relay via reactions (Kholtien), attachment / reaction / edit tools (nazbav).
All four use Claude Code as the chat-driven AI; the Channels-API-based ones (this project, nazbav, Kholtien) need the
--dangerously-load-development-channelslaunch flag. The flag value must beplugin:<plugin-name>@<marketplace>(notserver:<mcp-server-name>) — CC's matcher splits the server-id on:and rejects theserver:form for plugin-loaded MCP servers. Without the correct flag, inbound matrix events get silently dropped. The persistent setting"channelsEnabled": trueis additionally required on Team/Enterprise tiers and on any account with a managed-settings policy file present; on a plain personal account with no managed settings it's a no-op. This plugin's wizard sets it defensively anyway and prints the shell-rc alias for the flag; the others leave both as manual steps.Full comparison + capability matrix on the project page.
Install
1. Get the plugin
Option A — Claude Code plugin marketplace (recommended for end users):
claude plugin marketplace add arikw/claude-code-matrix-bridge
claude plugin install rx-claude-matrix-bridge@arikw
This drops a pre-bundled copy under ~/.claude/plugins/.../rx-claude-matrix-bridge/.
No npm install needed — dist/server.js and dist/daemon.js are committed as
single-file esbuild bundles with all runtime deps inlined.
Option B — git clone (for development, or if you want to rebuild from source):
git clone https://github.com/arikw/claude-code-matrix-bridge.git
cd claude-code-matrix-bridge
npm install
npm run build # produces dist/server.js + dist/daemon.js
The runtime auto-detects: dist/server.js if present (production / built dev
checkout), otherwise spawns tsx server.ts directly (unbuilt dev checkout).
2. Launch Claude Code with the channels flag
cd /path/to/your/project
claude --dangerously-load-development-channels plugin:rx-claude-matrix-bridge@arikw
The first launch auto-spawns the daemon. Subsequent TUIs connect to the running
daemon over ~/.claude/channels/rx-claude-matrix-bridge/daemon.sock.
Make this permanent — wrap Claude Code in a shell alias so you don't forget the flag:
alias claude='command claude --dangerously-load-development-channels plugin:rx-claude-matrix-bridge@arikw'(the setup wizard in step 3 will print this exact line tailored to your shell's rc file)
The matching persistent setting (
"channelsEnabled": truein~/.claude/settings.json) is set automatically by the setup wizard in step 3, so you don't need to edit settings.json yourself. On Team/Enterprise tiers the setting may be blocked by your admin — check with them if/mx-link-chatstill warns about channels-capable=false after setup.
3. Run /mx-link-chat — first-run onboarding kicks in automatically
Inside the TUI, run:
/mx-link-chat
On a fresh install, the bridge has no Matrix credentials yet, so the slash command
won't link anything — instead it prints a one-line setup instruction with the
absolute path to bin/mx-setup for your install. Run that wizard in a separate
terminal; it walks you through:
- Bot Matrix user ID (
@yourbot:server.tld) - Owner Matrix user ID (your personal account — the only sender allowed to drive Claude)
- Bot password — wizard performs a one-shot login to obtain a long-lived access token
- Optional: create the bot account itself via the Synapse admin API (only offered if the homeserver is Synapse and the owner is admin; skipped silently on Conduit/Tuwunel/etc.)
Wizard writes ~/.config/rx-claude-matrix-bridge/config.env (chmod 0600). Passwords
are read with read -s and never stored. Re-run safely — existing values become
prompt defaults.
After the wizard finishes, fully exit and relaunch Claude Code (the MCP server
loaded its config at startup), then run /mx-link-chat again. This time it shows
an interactive picker:
- existing rooms the bot has joined (with current link status)
- "create new room" → bot creates one, invites
MATRIX_OWNER, returns the room id - accept the invite in your Matrix client to start receiving messages
After linking, every message in that room routes to this session_id. On
claude --resume <session_id> later, routing resumes. If the TUI is dead, the
daemon spawns headless claude --print --resume <sid> and posts the reply back
to the room.
Don't have a bot Matrix account yet? Register one separately (via element.io or your homeserver's UI / admin tool) before running the wizard. The wizard can only create accounts on Synapse homeservers where your owner account is admin; for everything else (Conduit / Conduwuit / Tuwunel / Dendrite / hosted matrix.org / etc.), create the bot manually first.
4. Enable the statusLine indicator (optional but recommended)
Inside the TUI, run:
/mx-enable-statusline
Installs 🔗 mx:<room> / ✏️ indicators into the current project's
.claude/settings.json. The glyph also flips to other states so you spot problems
immediately:
| Glyph | Meaning |
|---|---|
🔗 mx:<room> |
Linked + healthy |
✏️ |
Owner is typing in the linked room |
⛓️💥 mx:<room> |
Claude Code was launched without --dangerously-load-development-channels plugin:rx-claude-matrix-bridge@arikw (matrix → TUI inbound silently dropped) |
⚙ mx:needs-setup |
config.env missing or has placeholder values (run bin/mx-setup) |
🔄 mx:restart-claude-code |
Plugin was updated mid-session and the live MCP server is stale (fully restart Claude Code) |
Power-user shortcut: manual setup (skip the wizard)
If you prefer to write config.env by hand:
mkdir -p ~/.config/rx-claude-matrix-bridge
cp config.env.example ~/.config/rx-claude-matrix-bridge/config.env
chmod 0600 ~/.config/rx-claude-matrix-bridge/config.env
$EDITOR ~/.config/rx-claude-matrix-bridge/config.env
Required keys: MATRIX_HOMESERVER, MATRIX_USER_ID, MATRIX_ACCESS_TOKEN, MATRIX_OWNER.
Get an access token via curl:
HS=https://matrix.example.org
BOT_USER=yourbot
BOT_PASS='replace-me'
curl -s -X POST "${HS}/_matrix/client/v3/login" \
-H 'Content-Type: application/json' \
-d "$(jq -nc --arg u "$BOT_USER" --arg p "$BOT_PASS" \
'{type:"m.login.password",
identifier:{type:"m.id.user",user:$u},
password:$p,
device_id:"matrix-bridge",
initial_device_display_name:"rx-claude-matrix-bridge"}')" \
| jq -r '.access_token'
Then continue with step 3 (/mx-link-chat).
Tools (exposed to Claude inside the TUI)
| Tool | Purpose |
|---|---|
reply(chat_id, text) |
Post m.text to a matrix room. Use chat_id from the inbound <channel> tag. |
link_chat({room_id?, name?, topic?}) |
Bind current session to a matrix room. With room_id: join (idempotent) + register. Without: create new room with name (default = cwd basename), invite owner, register. |
link_status() |
Show the current session's link, if any. |
unlink_chat() |
Remove the current session's link. |
list_rooms() |
All rooms the bot has joined, with link status (session_id + cwd). |
Slash commands
| Command | Effect |
|---|---|
/mx-link-chat |
Interactive room-link picker. Lists rooms, shows link state, prompts via AskUserQuestion. |
/mx-enable-statusline |
Install statusLine indicator into the current project's .claude/settings.json. |
Link semantics
Daemon enforces 1 session ↔ 1 room by removing any prior row that shares either side of the new pair:
| Scenario | Effect |
|---|---|
| Same session, same room | Idempotent — created_at refreshed |
| Same session, different room | Old room becomes orphan; session bound to new room |
| Different session, same room | Old session loses link; room points to new session |
| Different session, different room | Both kept; no conflict |
TUI ↔ Matrix recap
When you switch channels (typed on TUI, then ping from matrix — or vice versa),
the bridge auto-prepends a [matrix-bridge recap-since <timestamp>] instruction
that asks Claude to recap activity on the channel you just left before answering.
Implicit; no magic-word handshake.
The instruction also asks Claude to classify the incoming message as a substantive request vs a presence-only ping (e.g. "hi", "back", "I'm here"). On presence-only pings, Claude replies with just the recap plus a single line saying whether your attention is required (a pending question, a blocked decision) — no invented follow-up questions.
TUI prompt mirror
Every TUI-typed prompt is mirrored to the linked matrix room as [TUI] <prompt>
(m.notice). A background pinger shows m.typing while Claude processes.
Channel-injected blocks are stripped from prompts before mirror so they don't
echo back. Unlinked sessions don't mirror.
How it works
Three processes, three hops:
matrix client <-> matrix homeserver (m.room.message, /sync long-poll)
matrix homeserver <-> daemon.ts (/sync inbound, PUT /rooms/.../send outbound)
daemon.ts <-> server.ts (one per TUI) (AF_UNIX socket, line-JSON: inbound / reply / link_chat)
server.ts <-> Claude Code TUI (stdio MCP: notifications/claude/channel + reply tool)
daemon.ts — always-on Node process. Owns the matrix /sync long-poll.
Maintains links.tsv (session_id <-> room_id mapping). On each m.text from
MATRIX_OWNER:
- Lookup
links.tsvfor the room. - Linked: broadcast the inbound event to that session's AF_UNIX socket.
If the socket is unreachable (TUI offline), spawn
claude --print --resume <sid> --add-dir <cwd>headlessly and post the assistant text back to the room. - Orphan (room not in
links.tsv): drop + log a warn pointing the user to/mx-link-chat. The daemon used to auto-route orphan rooms to the sole registered TUI as a convenience, but that caused cross-talk across multi-host setups so it was removed in v0.4.22.
Daemon also debounces matrix m.typing (2s onset, refreshed every 20s, off
on reply or session_stopped) so the room shows a typing indicator while
Claude is processing.
server.ts — stdio MCP server, one per Claude Code TUI. Reads the real
session_id from the SessionStart-hook-written sessions/<cc_pid>.json,
spawns the daemon if not already running, registers (session_id, cwd)
with the daemon. Detects whether Claude Code was launched with the
channels flag (via /proc/<ccPid>/cmdline) and writes the
channels-capable/<sid> flag for the statusLine to read. Exposes the
reply, link_chat, link_status, unlink_chat, list_rooms tools;
forwards daemon-pushed inbound events as notifications/claude/channel
into the live TUI conversation.
State files at ~/.claude/channels/rx-claude-matrix-bridge/
daemon.pid, daemon.sock, daemon.log # daemon process
server.log, server.log.daemon-stdio # MCP server logs
since-token # matrix /sync cursor
links.tsv # session_id ↔ room_id ↔ cwd ↔ name ↔ created
sessions/<cc_pid>.json # SessionStart hook writes real session_id here
needs-setup # present if config.env missing/invalid (read by statusLine + tools)
channels-capable/<sid> # true/false per launched session
room-names/<safe_room_id> # cached matrix room display name
last-tui-prompt/<sid> # iso ts of last UserPromptSubmit per session
last-matrix-msg/<sid> # iso ts of last inbound per session
typing/<safe_room_id> # 'true' flag while owner is typing in room
tui-pinger.pid # mirror typing pinger
plugin-root # absolute path to repo (self-locate)
Troubleshooting
| Symptom | Check |
|---|---|
StatusLine shows 🔄 mx:restart-claude-code |
A newer plugin version is installed on disk than the one Claude Code currently has loaded — happens after claude plugin update without a full restart. Fully exit Claude Code (not just /mcp reconnect) and relaunch with the channels flag. |
StatusLine shows ⚙ mx:needs-setup |
config.env missing or has placeholder values. Run bash /path/to/repo/bin/mx-setup and relaunch Claude Code. |
/mx-link-chat returns "bridge is not configured" |
Same as above. The error message includes the absolute path to bin/mx-setup. |
StatusLine shows ⛓️💥 |
Claude Code launched without the matrix-bridge channels flag. Relaunch with --dangerously-load-development-channels plugin:rx-claude-matrix-bridge@arikw. |
/mx-link-chat output includes "WARNING: launched WITHOUT --dangerously..." |
Same as above. |
MCP not connecting (/mcp shows nothing) |
Confirm .mcp.json is present in cwd, flag passed, claude /mcp reload. |
| Channel events not arriving but flag is set | Confirm "channelsEnabled": true in ~/.claude/settings.json. Tail daemon.log for inbound→tui entries; server.log for DBG mcp.notification SENT. |
MATRIX_HOMESERVER unset |
Bot couldn't read config.env. Check path + 0600 perms. |
| 401 in daemon log | MATRIX_ACCESS_TOKEN expired or wrong. Re-run the setup wizard (bin/mx-setup) — it'll obtain a fresh token via the bot's password. |
| Bot ignores invites | Invites must come from MATRIX_OWNER. Non-owner invites are logged as ignoring invite room=... (not from owner). |
| Reply fails | Bot must be a member of the target chat_id. Reply tool returns daemon error to Claude. |
| Daemon won't start | Check ~/.claude/channels/rx-claude-matrix-bridge/daemon.log. Stale daemon.pid for a dead process? Daemon checks via kill -0 and clears stale pid. |
| Headless fallback not firing | Daemon needs claude in PATH. which claude must resolve. |
| Typing indicator stuck for 5 min | Stop hook not firing or daemon didn't receive session_stopped. Check hook.err. |
Uninstall / cleanup
# Stop the daemon
kill "$(cat ~/.claude/channels/rx-claude-matrix-bridge/daemon.pid 2>/dev/null)" 2>/dev/null
# Remove all state (incl. links + logs)
rm -rf ~/.claude/channels/rx-claude-matrix-bridge
# Remove config (incl. credentials)
rm -rf ~/.config/rx-claude-matrix-bridge
# Remove repo
rm -rf /path/to/claude-code-matrix-bridge
# Drop the alias from your shell rc if you added one
Security
Read this whole section before running. The bridge is a remote-code-execution surface gated entirely on matrix account integrity.
Trust model
- Owner account = full control. Anyone who controls
MATRIX_OWNER's matrix account can send messages that trigger Claude Code turns — including ones that invoke theBash,Edit,Writetools. Use a strong password + 2FA on that account. - Bot account = posting + room membership. Compromise leaks message contents and lets attacker post as the bot. Use a dedicated account; don't reuse the owner account.
- Homeserver admin sees all traffic (rooms are plaintext). Use a homeserver you trust, or self-host.
Hardening checklist
| Item | Why |
|---|---|
| Dedicated matrix account for the bot, not your personal one | Token compromise contained to bot |
Strong password + 2FA on MATRIX_OWNER |
Owner takeover = RCE on daemon host |
chmod 0600 config.env (script does this) |
Token = password equivalent |
Don't launch Claude Code with --dangerously-skip-permissions when using the bridge |
Matrix-triggered Bash calls would skip the permission prompt; same goes for --print headless turns (they inherit) |
Don't set MX_CLAUDE_PERMISSION_MODE=bypassPermissions unless you have an offline / sandboxed host |
Headless matrix-triggered turns will run Bash/Edit/Write without prompting. Same RCE class as --dangerously-skip-permissions. The wizard requires a double confirmation if you pick it. |
| Use a self-hosted homeserver or one whose admin you trust | Plaintext = admin reads everything |
| Set up billing alerts on your Anthropic account | Each matrix msg = LLM call = $. Owner-account compromise can spike spend. |
Treat .mcp.json + hooks/ as security-sensitive |
Anyone with write to the repo can change MCP server command → arbitrary code next claude launch |
Built-in protections
- Access token read from
chmod 0600config; never logged. - Inbound delivery gated on
sender == MATRIX_OWNER. Other senders silently ignored. - Auto-join only fires when the invite sender is
MATRIX_OWNER. - AF_UNIX socket is
chmod 0700(owner-only). - C0 control chars +
0x7fstripped from inbound + reply bodies (redactControls). - Reply text truncated to 16 KiB.
Known limitations (v0.4.24)
- Plaintext rooms only. E2EE via olm/megolm sidecar is on the roadmap.
- Single owner. Multi-user support not yet.
- No room ACLs. Any room the bot is in routes to owner-sender msgs.
- State dir is
chmod 0755parent (default umask). Contents are not secret but include room IDs, session IDs, timestamps. If you share the host, considerchmod 0700 ~/.claude/channels/rx-claude-matrix-bridge.
Roadmap
- E2EE rooms (olm/megolm sidecar). Two of the alternative Matrix bridges already have this; we don't yet.
- Permission relay — approve
Bash/Edit/Writetool calls from Matrix (👍/👎 reactions or text reply). Kholtien/claude-connect-matrix-integration has this; we'd implement it differently to fit the multi-session design. - Richer message tools —
edit_message,react,download_attachmentso Claude can edit, react, and pull attachments from rooms. - Pairing flow for multi-user — allow more than one owner via per-session pairing codes (nazbav's approach).
- systemd / launchd unit for the daemon — out-of-the-box always-on without manual nohup tricks.
- POSIX-sh-only mode for hooks + bin scripts — drops the bash dependency for minimal-container setups.
Layout
.claude-plugin/ plugin.json, marketplace.json
.mcp.json stdio MCP server registration
daemon.ts source: always-on Matrix daemon
server.ts source: MCP relay client (per-TUI stdio)
protocol.ts source: shared AF_UNIX line-JSON types
build.mjs esbuild config (npm run build → dist/)
dist/ pre-bundled JS shipped in the plugin (server.js, daemon.js)
hooks/ session-start.sh, user-prompt-submit.sh, stop.sh
bin/ mx-setup, mx-status-line, mx-tui-pinger, mx-enable-statusline
commands/ mx-link-chat.md, mx-enable-statusline.md
config.env.example copy to ~/.config/rx-claude-matrix-bridge/config.env
License
MIT — see LICENSE.
More projects
See all →2026
RX Browser Bridge
Drive your own already-open browser from your AI coding agent (any MCP client) via a self-hosted relay + MV3 extension. Tools: screenshot (+full-page), navigate, click, fill, query, new_tab, evaluate (arbitrary JS, even on CSP-locked sites via userScripts). Toolbar activity flash + sqlite audit log for visibility.
2
2020
Extension Page Context Runner
Run functions in page context directly from a browser extension content script
27
2020
Outlook Duplicated Items Remover
A VBA script that finds and moves duplicated items in selected outlook folders
21
2023
RX Input Layout Switcher
Alt+Shift keyboard layout switcher GNOME Shell Extension
16 9.6K
2025
Podman OpenRC Restart Policy Manager
This OpenRC init script manages the automatic startup and shutdown of Podman containers with the `restart-policy=always` on system boot or shutdown
5
2022
Express Response Hooks
Extends Express.js with response hooks
4 149.3K 5.5K/mo