dis-claude

dis-claude

Connects Discord to Claude Code, turning forum threads into interactive sessions. Supports approvals, live progress, slash commands, and terminal access.

Category
访问服务器

README

dis-claude

Run Claude Code from your phone. One Discord forum thread is one Claude Code session — reports arrive on their own, approvals are two taps, and it is still the same claude process you can tmux attach to from your terminal.

License: MIT Python 3.10+ discord.py

日本語 · README.ja.md | 中文 · README.zh-CN.md | 한국어 · README.ko.md


How this differs from the other Claude Code ↔ Discord bridges

There are already a good number of these, and several are excellent. Read this section first so you can pick the right one rather than the first one.

The projects worth comparing against include raylin01/DisCode, DoBuDevel/discord-agent-bridge, chadingTV/claudecode-discord, ecmulli/claudecord, fredchu/discord-claude-code-bot, thcapp/claude-discord-bridge, ebibibi/claude-code-discord-bridge and JessyTsui/Claude-Code-Remote.

The three real differences

1. Reporting comes from Claude Code's hook lifecycle — all of it.

Most bridges get their output one of three ways: polling tmux capture-pane (discord-agent-bridge samples every 30 seconds and diffs the screen), streaming a subprocess driven by the Agent SDK (claudecode-discord), or wiring a couple of hooks (DisCode uses PreToolUse, SessionStart, SessionEnd).

This project subscribes to the whole lifecycle:

SessionStart · UserPromptSubmit · Stop · StopFailure · Notification · TaskCreated · TaskCompleted · PostToolUse · PostToolUseFailure · SubagentStart · SubagentStop · PreCompact · PostCompact · PermissionDenied · CwdChanged · SessionEnd · PermissionRequest

That is why you do not have to write "please keep the user informed" in a system prompt. Reporting is not something the model chooses to do, so it cannot be forgotten and it cannot be overdone. Screen polling cannot see task boundaries or which subagent is doing what; this can.

2. It is your actual CLI session, not a re-implementation of it.

Claude Code runs inside tmux exactly as you would run it yourself. So:

  • tmux attach -t dis_<thread-id> and carry on typing, mid-session.
  • The interactive TUI works from Discord — /cc-cmd model, then /cc-key Down Enter.
  • The bot can restart, or crash, and your session keeps running. On the way back up it re-discovers live sessions from tmux list-sessions.

Bridges built on the Agent SDK run a Claude, not your Claude Code session. That is a legitimate design with its own advantages; it is simply a different thing, and worth knowing before you choose.

3. Approvals answer the CLI's own permission prompt.

When you press Allow, the bot sends Enter into the real permission prompt sitting in your terminal. Deny sends Escape. Claude Code's permission system stays in charge and stays authoritative — there is no --dangerously-skip-permissions, and no bot-side allowlist standing in for it.

On top of that sits a policy layer, because being asked about everything is how approval stops working:

Situation Result
Not destructive allowed silently
Destructive, outside your protected paths allowed (rm -rf ./build in a repo)
Destructive and aimed at a protected path ask, with the full command
rm -rf /, rm -rf $VAR/, mkfs, writes to /dev/sda always ask

Uncertainty always resolves to ask. A missing config, an unparseable command or an exception never produces an allow.

Everything else that is different

  • Claude can ask you something. The ask_user MCP tool blocks until you answer, and renders your options as buttons. Most bridges are one-directional.
  • Live progress in a single edited message — task counter, the last few tool calls, and one line per running subagent. Not a wall of new messages.
  • Secrets are masked before a command reaches your phone: bearer tokens, --password, sk-/ghp_/xox/AKIA shapes, and credentials in URLs.
  • Nothing is ever truncated. Long approvals and reports are posted whole, as file attachments. Half of a diff is not something you can approve.
  • The hooks import nothing but the standard library and always exit 0. If the bot is down or the queue is unwritable, Claude Code does not notice.
  • No network between the pieces. Hooks and bot talk through a file queue with atomic writes, so a bot restart loses nothing and a hook never blocks.
  • Localised UI — English and Japanese ship in the box, and a language is one dict in i18n.py.
  • Written in Python. Every other bridge I found is TypeScript/Node.

Where the others are stronger

Being fair about this is more useful to you than a clean sweep would be.

  • Multiple machines from one server — DisCode and claudecode-discord both do this properly. Here, one bot serves one machine.
  • Container isolation — discord-agent-bridge can run agents in Docker.
  • Non-Discord transports — Claude-Code-Remote also does email and Telegram.
  • Windows — this project needs tmux, so it wants Linux, macOS or WSL.

What it looks like

#claude-code (forum channel)
│
├─ 🟢 [3/5] fix token expiry in login.py          ← name and tag update themselves
│   │
│   │  you › the session keeps 401ing after an hour, have a look
│   │
│   │  🤖 `[1/5]` start: reproduce the expiry
│   │     -# 🔎 search: refresh_token
│   │     -# 📄 read: auth.py
│   │     -# 👤 agent 1 🛠️ bash: pytest tests/test_auth.py -x
│   │
│   │  ┌────────────────────────────────────────┐
│   │  │ **Approval needed** `Bash` — rm -r / -f │  ← red card, always rings
│   │  │ ```bash                                 │
│   │  │ rm -rf ~/.config/myapp/cache            │
│   │  │ ```                                     │
│   │  │  [ Allow ]  [ Deny ]                    │
│   │  └────────────────────────────────────────┘
│   │
│   │  🤖 Fixed. `refresh_token` was compared against `iat` instead of `exp`,
│   │     so any token older than an hour failed the check. auth.py:88.
│   │     Tests pass.
│
└─ ✅ [done] audit the queue drain path

Quick start

Five minutes, assuming you already have Claude Code working.

1. Install

pip install git+https://github.com/0rnot/dis-claude

(A PyPI release is coming; until then, install from the repository.)

You also need tmux (apt install tmux / brew install tmux) and, obviously, the claude CLI on your PATH.

2. Make a Discord bot

  1. Discord Developer PortalNew ApplicationBotReset Token, and keep the token.
  2. Under Bot, enable Message Content Intent.
  3. OAuth2 → URL Generator: scopes bot and applications.commands; permissions View Channels, Send Messages, Send Messages in Threads, Create Public Threads, Manage Threads, Embed Links, Attach Files, Add Reactions, Read Message History, Manage Messages.
  4. Invite it to your server.
  5. Create a Forum channel for it. Right-click → Copy Channel ID (Developer Mode must be on in Discord's Advanced settings).

3. Wire it up

dis-claude init

That writes ~/.dis-claude/config.json, merges the hook entries into ~/.claude/settings.json (backing it up first, and leaving anything already there alone), and registers the MCP server with claude mcp add.

Then fill in the two ids and export your token:

$EDITOR ~/.dis-claude/config.json   # forum_channel_id, owner_user_id
export DISCORD_TOKEN='...'

4. Check and run

dis-claude doctor
dis-claude bot

5. Use it

Post a thread in the forum channel and send a message. That first message starts a session and becomes its first instruction. Everything after that goes to the running session.

To choose a working directory, start the thread name with cwd::

cwd:/home/you/projects/api   token expiry investigation

Controls

Want to Do
Start a session Post in the forum and send a message
Give an instruction Send a message in the thread
Send something long /cc-long (opens a dialog; newlines survive)
Answer a question Tap a button, or reply, or long-press → Answer this
Approve an action Tap Allow / Deny
Send a file Attach it — Claude receives the path
Run a slash command /cc-cmd compact (no leading /)
See the terminal /cc-screen
Drive the TUI /cc-key Down Down Enter
Review changes /cc-diff
Interrupt /cc-stop, or long-press → Interrupt
End the session /cc-end
List sessions /cc-status

Reactions tell you what happened: ⌨️ delivered, ✅ answer accepted.


How it works

   Discord                    file queue                  your machine
  ┌────────┐            ┌────────────────────┐          ┌───────────────┐
  │ thread │◄──────────►│ queue/outbox  ───► │◄────────►│ Claude Code   │
  │        │            │ queue/inbox   ◄─── │  hooks   │   in tmux     │
  │ button │            │ queue/files        │   MCP    │               │
  └────────┘            └────────────────────┘          └───────────────┘
       ▲                          ▲                             ▲
       │                          │                             │
   dis-claude bot            atomic writes                tmux send-keys
   (discord.py)          (no network at all)           paste-buffer -p

Three processes that only ever meet through files:

  • dis-claude bot — long-lived. Drains the outbox, renders Discord, and pushes your input into tmux.
  • dis-claude hook <Event> — spawned by Claude Code per event. Standard library only; always exits 0.
  • dis-claude mcp — spawned by Claude Code. Provides ask_user, tell_user and set_thread_title.

Writes go to a unique temporary name and are then os.replace-d into position, so a reader never sees a partial file. Nothing listens on a port.

Details that took a while to get right

Documented because they are not obvious, and because if you fork this you will otherwise rediscover them the hard way.

  • Thread renames are limited to two per ten minutes. Ignore it and you get a 429, after which every later rename is dropped. Renames are debounced at 330 seconds and state is shown with forum tags instead, which are not rate-limited.
  • Multi-line input needs bracketed paste. tmux paste-buffer -p, then Enter after a short pause. Without -p, a message's first line submits and the rest is stranded. Without the pause, a long paste submits half-read.
  • tmux new-session needs -d. Without it tmux attaches to the current terminal — and under systemd there isn't one, so it dies with open terminal failed: not a terminal.
  • The thread id must be passed as env VAR=..., not export. A running tmux server hands new sessions the environment it started with, so an exported variable never arrives.
  • A nested claude emits SessionEnd too. claude mcp list inside a session fires it, inheriting the same thread id. Acting on it would kill the tmux session the real one lives in, so unbound SessionEnd events are dropped.
  • Double-press protection cannot be a remembered set. Sets overflow, and an evicted entry makes a days-old card look unanswered. The test is "is this thread still waiting on this ask" — one entry per thread, cleared on answer and expiry.
  • Stop can fire before the final message is written. Reading the tail naively reports the previous turn's answer and silently loses this one. Freshness is judged by position in the transcript, plus a content fingerprint, with the read window widened until the turn actually fits.
  • A periodic task that raises stops forever, silently. Every loop is wrapped.

Configuration

~/.dis-claude/config.json. See docs/configuration.md for the full reference; the parts you are most likely to touch:

{
  "language": "en",                  // or "ja"
  "discord": {
    "forum_channel_id": "...",
    "owner_user_id": "..."           // leave empty and anyone in the channel can drive it
  },
  "launcher": {
    "default_cwd": "~/projects",
    "claude_command": "claude"
  },
  "permission": {
    "auto_allow": true,              // false = confirm everything
    "protected_paths": ["~/.ssh", "~/.gnupg", "~/.claude"],
    "deny_tools": []                 // e.g. ["Bash"] to never approve shell remotely
  },
  "thread": {
    "tags": { "working": "", "waiting": "", "done": "", "error": "" }
  }
}

Forum tags are optional. Create four tags in the channel, paste their ids, and the channel list shows session state at a glance.


Security

Read this part. You are putting a shell on your phone.

  • Set owner_user_id. Without it, anyone who can see the channel can drive Claude Code on your machine.
  • A private channel in a private server. This is not a public bot.
  • Tapping is cheaper than typing, which cuts both ways: approving on a phone is a smaller act than approving at a terminal, so it is easier to approve without reading. That is exactly why approval cards carry the complete command, never a summary. Read it before you tap.
  • Secret masking is best-effort. It catches common shapes; it is not a guarantee, and it is not a reason to route production credentials through here.
  • deny_tools is the hard stop. ["Bash"] means shell commands can never be approved from Discord regardless of anything else.
  • The bot only reads attachment paths inside its own home directory. Queue messages cannot name arbitrary files.

Troubleshooting

Nothing appears in Discord. Check ls ~/.dis-claude/queue/outbox — if files are piling up, the bot is not draining them. If it is empty and nothing arrived, the hooks are not firing: dis-claude doctor.

Hooks are not firing. hooks.<Event> must be a list of matcher groups. An object there is ignored without a word. Re-run dis-claude init.

A hook is misbehaving. They swallow their own exceptions on purpose, but they leave a trace: ~/.dis-claude/hook-errors.log.

The session starts but messages do not reach it. tmux ls — you should see dis_<thread-id>. If it is missing, the launch failed; /cc-end and send again.

The thread name stopped updating. You hit the rename limit. It recovers on its own within about ten minutes.

ask_user never returns. Look for an ask entry in queue/outbox. If it is not there, the MCP server is stuck rather than the bot.


Contributing

Issues and pull requests are welcome — see CONTRIBUTING.md. Translations are especially welcome and take about ten minutes: copy the EN dict in src/disclaude/i18n.py, translate the values, add it to CATALOGS. Missing keys fall back to English, so a partial translation is immediately useful.

License

MIT. See LICENSE.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选