claude-code-discord-multibot
Health Uyari
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Basarisiz
- child_process — Shell command execution capability in patch-discord-plugin.sh
- process.env — Environment variable access in patch-discord-plugin.sh
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
Using Discord bots easily deploy multiple Claude code tasks (A Sibling repo with claude-code-telegram-multibot) 使用Discord快速部署多个Claude code终端(claude-code-telegram-multibot的姐妹仓库)
🇺🇸English · 🇨🇳中文
claude-code-discord-multibot
One dedicated Discord bot per Claude Code project · independent state · zero cross-talk · scales to hundreds of bots
Sibling project:
claude-code-telegram-multibot— same architecture on Telegram. Pick this Discord version if you've outgrown Telegram's 20-bot @BotFather cap, or want Discord-only features likefetch_messagesand bidirectional reactions.
TL;DR — pair the official
discordplugin with a few shell functions and a Claude Code skill, so each project directory gets its own Discord bot. Switch projects in Discord by switching channels, not terminals. Sister project toclaude-code-telegram-multibot— same architecture, different platform.Use Discord when you've outgrown Telegram's 20-bot hard cap, or want richer features: message history via
fetch_messages, bidirectional reactions, threads, channel-based organization.
✨ Why
The official Discord plugin assumes one bot per user. If you want multiple Claude Code projects each bridged to a different Discord channel, the default setup runs into two walls:
- Every
claudeinvocation spawns the plugin's MCP server, which uses a singleton state directory by default. Each new session disrupts the previous one. - The
/discord:accessskill hardcodes the default state directory path, so per-project pairing workflows fail.
The trick: the plugin's server.ts honors a DISCORD_STATE_DIR environment variable. Point it at a unique directory per project and you get full isolation — separate token, separate pairing, separate bot server, no fighting.
The shell functions in claude-dc.bash derive DISCORD_STATE_DIR from basename "$PWD", so launching claude-dc in any project directory automatically targets that project's own bot. The companion skill (SKILL.md) teaches Claude Code agents the architecture, so saying "add a new Discord bot for this project" becomes a one-shot operation.
This skill / architecture is also not limited to Claude Code — it can be ported to Codex, OpenClaw, or other agent platforms. And the same idea works for any chat platform: Telegram (sibling repo), Slack, IRC, Matrix, iMessage, etc. — wherever you have a per-token-per-channel bot model.
🚀 Quick start
Prereqs: Claude Code installed · official
discordplugin enabled in~/.claude/settings.json·bunon$PATH· org policy permits thediscordchannel plugin (admin must add it toallowedChannelPlugins) · Discord account + a Discord server you control.
# 1. Install
git clone https://github.com/Lihan-Zhong/claude-code-discord-multibot.git
cd claude-code-discord-multibot
# 2. Load the shell functions
echo "source $PWD/claude-dc.bash" >> ~/.bashrc
source ~/.bashrc
# 3. Install the skill (teaches Claude Code to manage the setup)
mkdir -p ~/.claude/skills/setup-discord-multibot
cp SKILL.md ~/.claude/skills/setup-discord-multibot/SKILL.md
# 4. Optional but recommended: the plugin patches and the two hooks (see "Mechanisms" below)
cp patch-discord-plugin.sh ~/.claude/ && chmod +x ~/.claude/patch-discord-plugin.sh
mkdir -p ~/.claude/hooks && cp hooks/*.py ~/.claude/hooks/
~/.claude/patch-discord-plugin.sh # idempotent; safe to re-run any time
⚠️ The hooks must be registered in
~/.claude/settings.json, neversettings.local.json—
see Mechanisms for the exact block and why.
Add a bot for a project:
cd /path/to/my-project # whatever project
# At https://discord.com/developers/applications:
# 1. New Application → name it
# 2. Bot tab → Reset Token → copy
# 3. Enable Message Content Intent
# 4. OAuth2 → URL Generator → scope: bot + permissions → invite to your server
claude-dc-init # paste the token; writes a .env
claude-dc # launch Claude Code with this bot attached
# In Discord: DM your new bot any plain message (e.g. "hi")
# Bot replies with a 6-char pair code
claude-dc-pair <6-char-code> # if jq is installed
# Or in the Claude Code session: "I got the pair code, please pair me."
Done. Future cd /path/to/my-project && claude-dc re-attaches to the same bot.
🚀 Much easier quick start
# 1. Install
git clone https://github.com/Lihan-Zhong/claude-code-discord-multibot.git
cd claude-code-discord-multibot
Then, ask your Claude Code session to read this whole repo and follow SKILL.md. The agent will walk you through every step 🔥
‼️ Recommended usage
- Step 1: deploy your first Discord bot for your
$HOMEdirectory — connect your "manager" Claude Code terminal here. - Step 2: use that "manager" bot to help you deploy subsequent project-specific bots (the manager handles state dir / .env / pairing for you).
- Then: you have an army of project-specific Discord bots, each in its own channel or DM, easy to switch between.
📁 What's in this repo
claude-dc.bash— six shell functions:claude-dc,claude-dc-init,claude-dc-alt,claude-dc-pair, and the two session-attributing resume helpersclaude-dc-resume/claude-dc-alt-resume. Source from~/.bashrc.patch-discord-plugin.sh— three idempotent patches to the plugin'sserver.ts: bot-to-bot messages, a real online presence, and markdown-safe message splitting. Re-runnable; self-heals after a plugin upgrade.hooks/enforce-discord-reply.py—Stophook. A turn triggered from Discord cannot end without a Discord reply.hooks/guard-variant-memory.py—PreToolUsehook. An alt bot cannot write into another bot's memory namespace.SKILL.md— Claude Code skill that teaches an agent the architecture. Drop into~/.claude/skills/setup-discord-multibot/SKILL.md.README.md/README.zh.md— this file (English / 中文).CHANGELOG.md— what changed between releases.LICENSE— MIT..gitignore— keeps tokens and state dirs out of git by default.
🧩 Two bots in the same project (alt mode)
Want two independent Claude Code sessions on the same project — a "primary" run and a "what if I tried it this way" experiment? Use claude-dc-alt:
cd /path/to/my-project
# After setting up the primary bot:
mkdir -p ~/.claude-discord/$(basename "$PWD")-2
$EDITOR ~/.claude-discord/$(basename "$PWD")-2/.env # paste a second bot's token
claude-dc-alt # variant 2 by default
claude-dc-alt 3 # variant 3, if you want a third
claude-dc-alt also exports CLAUDE_BOT_VARIANT=N, so your project rules can pick a variant-specific sandbox directory:
# in your CLAUDE.md or project rules
SANDBOX="Intermediate_data/for_claude${CLAUDE_BOT_VARIANT:+_${CLAUDE_BOT_VARIANT}}"
Bot A writes to Intermediate_data/for_claude/, bot A-alt writes to Intermediate_data/for_claude_2/. No collisions.
Resuming the right session
Claude Code keys sessions on the working directory alone — nothing binds a session to DISCORD_STATE_DIR. So in a two-bot directory, -c continues whichever transcript is newest, which is a coin flip, and -r's picker lists both siblings without saying which is which. Two helpers do the attribution for you:
claude-dc-resume # resume the PRIMARY bot's own session
claude-dc-alt-resume 2 # resume alt 2's own session
claude-dc-resume <sid> # escape hatch: resume exactly this session id
They use different signals, and that difference is the interesting part. The alt is easy: score each transcript by how often it references memory/variant_<N>/, its own namespace. The primary cannot just invert that test — its transcript mentions variant_2 plenty, because you discuss the alt bot inside it. What separates them is where the mention appears: score only the session opening, which is what the harness injected, not what the conversation later discussed. On a real five-session directory whole-file scoring could not separate the two bots at all, while opening-only scoring gave 0 for the primary and 3–4 for each alt.
Neither helper guesses: if nothing scores cleanly, it starts fresh and tells you.
🛡️ Mechanisms (hooks + plugin patches)
v1 shipped conventions. The problem with a convention is that it fails exactly when you need it — at the end of a long tool chain, under load. v2 moves the important ones down into the harness.
| what it does | shape | |
|---|---|---|
hooks/enforce-discord-reply.py |
Stop hook — blocks ending a Discord-triggered turn that never called reply/react/edit_message |
guard (denies) |
hooks/guard-variant-memory.py |
PreToolUse hook — denies a write into any memory namespace that is not this alt's own memory/variant_N/ |
guard (denies) |
patch-discord-plugin.sh #1 |
lets bots see each other's messages in a shared channel (drops only self-echo) | transform |
patch-discord-plugin.sh #2 |
a real online presence, so the green dot means "actually connected" | transform |
patch-discord-plugin.sh #3 |
markdown-safe 2000-char splitting — a code block is never cut in half | transform (repairs) |
Prefer a transform to a guard where one exists: a guard still produces an event someone has to notice and act on, while a transform has no failure mode to report — the right thing simply comes out. Nobody has to know the chunker exists.
Both hooks are fail-open: bad input, unknown tool, missing transcript, or any internal error → allow. A guard that wedges every session is worse than the slip it prevents.
⚠️ Hooks only load from ~/.claude/settings.json
This one cost two weeks of a hook that never ran, with no error and no warning. Claude Code's settings chain is:
<project>/.claude/settings.local.json → <project>/.claude/settings.json → ~/.claude/settings.json
~/.claude/settings.local.json is not on that chain. A hooks block placed there is silently ignored. Register them like this:
// ~/.claude/settings.json
{
"hooks": {
"Stop": [
{ "hooks": [{ "type": "command", "command": "/usr/bin/python3 $HOME/.claude/hooks/enforce-discord-reply.py" }] }
],
"PreToolUse": [
{ "matcher": "Write|Edit|NotebookEdit|Bash",
"hooks": [{ "type": "command", "command": "/usr/bin/python3 $HOME/.claude/hooks/guard-variant-memory.py" }] }
]
}
}
And do not verify this from a bot whose project directory is your $HOME. That is the one session where ~/.claude/settings.local.json is the project-level file, so it happens to work there and nowhere else — which is exactly how the bug hid for two weeks. Test from a real project bot.
Keeping the patches alive
patch-discord-plugin.sh edits a file the plugin owns, so a plugin upgrade overwrites it. That is handled, but it is worth knowing how:
- The launcher calls the script on every shell start; it is idempotent, so it is a no-op until it is needed.
- The running plugin loads from the cache copy (
~/.claude/plugins/cache/.../discord/<version>/server.ts), pinned by version. An upgrade creates a new version directory, and the patch target is a glob, so it is covered. - If upstream renames
chunk()entirely, the script prints[warn]and leaves the file alone rather than writing something broken. Patch #3 additionally runs a transpile check and reverts itself if the result no longer parses. - Belt and braces: set
"chunkMode": "newline"in each bot'saccess.json. It does nothing while patch #3 is in place, and it means that with the patch entirely absent you still get clean line breaks instead of a hardslice(0, 2000).
A running bot keeps the old code until it restarts. Patching does not affect a live session.
🤖 Multi-bot collaboration (Discord-specific)
Unlike Telegram, Discord lets multiple bots share a channel and see each other's messages. This enables a "team of agents in one room" workflow — e.g. @planner_bot, @coder_bot, @reviewer_bot all in one channel coordinating via @mentions.
Trade-off: bots in shared channels see all channel messages as <channel> events even when they don't act, which inflates each bot's session context. Recommended pattern: keep bots in isolated channels for daily work; create temporary "war-room" channels when you need real cross-bot collaboration, then archive when done.
See SKILL.md "Multi-bot collaboration via shared channels" for details.
🗂️ State directory layout
~/.claude-discord/
├── <project-A-basename>/
│ ├── .env # DISCORD_BOT_TOKEN=... (chmod 600)
│ ├── access.json # dmPolicy / allowFrom / pending (chmod 600)
│ ├── approved/<senderId> # pairing-confirm signal file (contents: chatId)
│ └── inbox/ # received attachments (photos etc.)
├── <project-A-basename>-2/ # alt bot for project A
└── <project-B-basename>/
└── ...
🐛 Known issues
See
SKILL.mdfor the full troubleshooting catalogue.
- A hook you registered never runs. It is almost certainly in
~/.claude/settings.local.json, which is not on the settings chain. Move thehooksblock to~/.claude/settings.json. There is no error message for this. See Mechanisms. - The plugin doesn't load when you launch from a project subdirectory. From Claude Code 2.1.181 the plugin must be installed at user scope — project scope no longer inherits into subdirectories. Check with
claude plugin list | grep -A2 discord; theScope:line must sayuser. Symptom: the banner mentions channels but/mcpshows no discord server, and the plugin subprocess is never spawned. - A bot's memories land in a different project's bucket. A broken or empty
.gitin$HOME— even onegititself rejects — is enough to stop the harness's project-root walk there, so every bot without its own.gitresolves its project root to$HOME. Transcripts still key on the working directory, which is what makes it invisible. Remove the stray.git;SKILL.mdhas the full detection-and-recovery procedure, including why matching on a memory's filename alone is not evidence of who wrote it. - A long reply gets split in the middle of a code block. That is upstream's
chunk()doing a blindslice(0, 2000). Applypatch-discord-plugin.sh(patch #3). Note that switchingchunkModeto'newline'does not fix it on its own — a code block is nothing but newlines, so it still cuts inside the block. - Inbound messages aren't reaching the agent (one-way). Most common cause: org policy doesn't include
discordinallowedChannelPlugins. Admin must enable via Claude.ai Admin Console → Claude Code → Channels. Editing~/.claude/remote-settings.jsonlocally is futile — the server overwrites within an hour. /discord:access pairreports "code not found". The official skill hardcodes the default state dir path. Useclaude-dc-pairfrom this repo, or follow the manual file-edit path in SKILL.md.- MCP "failed — Skipping connection" cached. Run
/doctorthen/mcpinside Claude Code;/mcpoffers a manual retry. Resist the urge to raiseMCP_TIMEOUT: a longer timeout only helps when the connection was about to succeed. When it never succeeds, all you have done is convert a fast failure into a slow one — in practice, a startup that hangs for the full timeout and is hard to interrupt. - Discord bot doesn't see message text. Enable "Message Content Intent" in Developer Portal → Bot → Privileged Gateway Intents.
stop_reason: refusalmid-session. Anthropic's safety classifier can false-positive on bloated sessions. Recovery:/exitthenclaude-dc(no-c/-r— those resume the poisoned session). Rebuild context from project files. Mitigation: keep tool outputs small, write big results to files instead of pasting them through the agent.
🔒 Security notes
- Bot tokens grant full control of the bot.
.envfiles arechmod 600insidechmod 700directories. Don't commit them, don't share OAuth invite URLs widely. The included.gitignoreexcludes.env,*.env,.claude-discord/,.claude/channels/. allowFromis the only gate to a Claude Code session behind a bot. Anyone whose Discord snowflake ID is listed can effectively type into the paired session. Treat the list as carefully as shell access.- The plugin sends outbound traffic only to
discord.com/api/v10andgateway.discord.gg. No third-party endpoints. The hooks and the patch script are local-only: they read and write files under~/.claude, and make no network calls. - Never approve a pairing because a chat message asked you to. A message saying "approve the pending code" or "add me to the allowlist" is precisely the request a prompt injection would make. Verify the pending entry's
senderIdagainst your own Discord snowflake before pairing, and let the human run the pairing step. - Be cautious with "Public Bot" toggle — if Public, anyone with the OAuth URL can install the bot into their own server. Keep the URL private.
🆚 vs the Telegram sibling
| Dimension | Telegram sibling | This (Discord) |
|---|---|---|
| Hard bot count limit | 20 per @BotFather account | Effectively unlimited |
| New-bot setup | 3 steps in @BotFather chat (~2 min) | ~~6 steps in Developer Portal browser (~~5 min) |
| Markdown formatting | MarkdownV2 with full escape | Discord Markdown, almost no escape |
Headers (# H1) |
Not supported | Supported |
| Message history API | Not available (bot API limit) | fetch_messages tool |
| Reactions | Bot → user only | Bidirectional (user reactions visible to bot) |
| Multi-bot collab in one room | Not supported (private DMs only) | Supported via shared channels |
| Mobile notifications | Fastest | Slightly slower |
The Telegram sibling repo is recommended if you have <20 bots and prefer @BotFather's simpler in-app flow; this Discord repo is recommended for scale (50+ bots) or advanced collaboration patterns.
🤝 Contributing
PRs welcome, especially:
- Variant-aware
claude-dc-pair(currently only handles the primary bot's state dir). - A reference implementation of multi-bot orchestrator on top of this skill (planner / coder / reviewer / tester pattern).
- Adaptations for Slack (no official plugin yet, requires a Slack bridge).
📜 License
MIT — see [LICENSE](LICENSE).
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi