> ## Documentation Index
> Fetch the complete documentation index at: https://felimet-hub.jmcores.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Claude Code Discord daemon bridge

> A 24/7 headless Discord to Claude Code bridge for Windows: a standalone process holds the gateway and spawns a headless claude -p per allowlisted DM.

`Claude Code` · `Discord` · `MCP` · `AI Agent` · `Windows`

## Overview

The official Discord channel plugin binds the Discord gateway to an MCP stdio pipe that dies the moment the terminal closes, so the bot is only online while Claude Code is open. This project runs `bot.ts` as its own long-lived process that holds the gateway connection itself. Each DM from an allowlisted user spawns a fresh headless `claude -p` loaded with the full global config (MCP servers, plugins, `CLAUDE.md`), and that process posts the reply back to Discord. A Windows logon-task daemon keeps it alive across terminal close, crashes, and reboots.

It is distributed both as an npm package (`bunx claude-discord-daemon` / `cdd`) and as a Claude Code plugin (the `/claude-discord-daemon:discord` command), under the MIT license.

## Core features

* Live named-tool status card: while a run is in flight, a single embed is edited in place and names the exact tool, skill, MCP, agent, or command that is running (for example `MCP matlab·evaluate`, `Bash`, `agent code-reviewer`, `skill xlsx`, `/commit`) instead of a vague "thinking"
* Reply overwrites the status card: on success the card is edited in place into the final reply, with a faint Discord `-#` footer (elapsed time and per-kind tool counts), so the chat keeps a single message; file, long, or multi-chunk replies go out as fresh messages (edits cannot attach files) and the card collapses to just the footer
* Rich output directives, each on its own line in the reply:
  * `[[embed: {json}]]` renders a colored embed card; the JSON may span multiple lines
  * `[[file: /absolute/path]]` attaches a produced file, verified for existence and size before sending
  * a trailing `[[buttons: A | B]]` line renders 2-5 real Discord choice buttons; the clicked label is fed back into the same session as the next message
* Attachment download: files the user uploads (≤ 50 MB) are downloaded to a shared inbox and their local paths are appended to the prompt for `claude` to `Read`
* Permission relay: a headless run's permission prompts become a Discord Allow / Deny button card that the owner clicks, with no trip back to a terminal
* Idle watchdog: no total time limit by default; a run is killed only when its event stream goes silent past `BOT_IDLE_TIMEOUT_MS` (a wedged process), never for merely taking a long time
* Natural-language new-conversation reset: a message that is entirely one of a few reset phrases drops the stored session, so the next message starts fresh
* Per-chat session continuity: each chat's session UUID is stored in `sessions.json` and later messages `--resume` it
* 24/7 daemon: a logon scheduled task, detached launch, restart on crash, and a single-instance loopback lock keep it running across terminal close, crashes, and reboots
* Portable: no hardcoded paths; the working directory defaults to the engine directory and every path is overridable via env vars

## Architecture

The boot chain that keeps it up is: logon task `ClaudeDiscordBridge` → `wscript launch.vbs` (detached) → `supervise.cmd` loop → `bun bot.ts`.

The bridge owns its own discord.js gateway instead of using the official channel engine because Anthropic gates any non-approved custom channel behind `--dangerously-load-development-channels`, and that flag shows a startup confirmation dialog. A headless daemon has no TTY to answer it, so it hangs. With `bot.ts` holding the gateway, no development-channel flag and no interactive confirmation are needed, and it can run unattended as a service.

Project files:

**claude-discord-daemon**

**bot.ts · long-lived Discord gateway process (the bridge engine)**

**perm-mcp.ts · permission relay MCP**

**cli.ts · cdd / claude-discord-daemon CLI**

**launch.vbs · launches the supervisor detached**

**supervise.cmd · restarts on crash**

**install-service.ps1 · registers the logon scheduled task**

**.env.example · environment variable template**

**commands**

**discord.md · the /claude-discord-daemon:discord command**

**skills**

**discord-presentation**

**SKILL.md · formatting and rich-output guide for Discord replies**

**.claude-plugin**

**plugin.json · plugin metadata**

**marketplace.json · single-plugin marketplace**

**videos**

**demo.gif · simple test demonstration**

**demo.mp4 · full-quality version**

## Tech stack

* Runtime: Bun ≥ 1.1.0, TypeScript
* Discord: discord.js v14
* Permission relay: `perm-mcp.ts`, built on the MCP SDK (`@modelcontextprotocol/sdk`)
* Daemon: Windows logon scheduled task, `launch.vbs`, `supervise.cmd`
* Distribution: npm package and Claude Code plugin

## Quick start

> [!WARNING]
>
> Windows 11 only: the scheduled-task, VBS, and cmd supervision chain is Windows-specific. You need [Bun](https://bun.sh) ≥ 1.1.0 with `bun` on `PATH`, an installed and logged-in Claude Code CLI (the bridge spawns `claude -p`), and your own Discord bot token. The bot must share a server with you, because Discord requires that before it lets you DM a bot.

**1. Write the bot token**

Before starting, put your bot token and sender allowlist in the bridge's state directory (default `~/.claude/channels/discord/`, overridable via `DISCORD_STATE_DIR`). Write the token to `~/.claude/channels/discord/.env`:

```
DISCORD_BOT_TOKEN=your_discord_bot_token
```

You can also set `DISCORD_BOT_TOKEN` directly in the process environment; a real env var wins over the `.env` file.

**2. Set the allowlist**

Create `~/.claude/channels/discord/access.json` and add your Discord user ID to `allowFrom`:

```json theme={null}
{
  "allowFrom": ["your_discord_user_id"]
}
```

Only DMs from user IDs in `allowFrom` are processed; everything else is ignored (fail-closed). The file is re-read on every message, so allowlist edits apply without a restart.

**3. Install and start (pick one)**

As an npm package:

```powershell theme={null}
# no-install, one-shot start
bunx claude-discord-daemon up

# or install globally and use the short alias cdd
npm i -g claude-discord-daemon
cdd up
```

As a Claude Code plugin:

```
/plugin marketplace add felimet/claude-discord-daemon
/plugin install claude-discord-daemon
```

Claude Code namespaces plugin commands with the plugin name, so the command is `/claude-discord-daemon:discord`. Drive the same daemon with it:

```
/claude-discord-daemon:discord up | down | status | logs
```

It runs the same `cdd` CLI under the hood, falling back to `bunx claude-discord-daemon` when `cdd` is not on `PATH`.

**4. Invite the bot and DM it**

Invite the bot into any Discord server you are also in, then DM the bot directly.

**5. Manage the daemon**

```
cdd up       # register the logon task + start detached
cdd status   # logon task / processes / log tail
cdd logs     # last 40 log lines
cdd down     # stop supervisor + engine
```

The bin names are `claude-discord-daemon` and `cdd` (identical).

## Configuration reference

Optional `access.json` keys, with the same semantics as the official plugin:

| Key | Default | Effect |
| - | - | - |
| `ackReaction` | hourglass reaction | Acknowledgement reaction on receipt; set `""` to disable |
| `textChunkLimit` | `2000` | Per-message character cap (max 2000) |
| `chunkMode` | `newline` | Chunking strategy: `newline` (paragraph-aware) or `length` (hard cut) |

Everything in the environment except `DISCORD_BOT_TOKEN` is optional; set any of these in the process environment to override the default:

| Variable | Default | Purpose |
| - | - | - |
| `DISCORD_BOT_TOKEN` | (required) | Discord bot token; if unset, read from `<state dir>/.env` |
| `DISCORD_STATE_DIR` | `~/.claude/channels/discord` | Directory holding `.env`, `access.json`, and the `inbox/` for downloaded attachments |
| `BOT_WS_DIR` | engine directory | Working dir for `sessions.json`, `outbox/`, and `bot.log` |
| `CLAUDE_BIN` | `~/.local/bin/claude` | Path to the `claude` executable the bridge spawns |
| `BOT_PERMISSION_MODE` | `auto` | Permission mode for spawned runs. `auto`: the auto-classifier gates each tool call; read-only passes, and destructive or self-modifying calls route to a Discord Allow / Deny button. Set `default` to re-harden so every approval-requiring tool goes through the button flow |
| `BOT_MODEL` | unset (account default) | Pin a Claude model, e.g. `claude-sonnet-5` / `haiku`, to cut cost |
| `BOT_TIMEOUT_MS` | `0` (uncapped) | Per-run wall-clock cap in ms; `0` means no cap, so long jobs are supported |
| `BOT_IDLE_TIMEOUT_MS` | `1800000` (30 min) | Kill a run after this many ms of stream silence (wedged-process watchdog) |
| `BOT_PERM_PORT` | `49223` | Loopback port for the permission-approval endpoint (`perm-mcp.ts` callback) |
| `BOT_PERM_TIMEOUT_MS` | `600000` (10 min) | An unanswered permission card auto-denies after this many ms |
| `BOT_LOCK_PORT` | `49222` | Single-instance loopback lock port |

New conversation: send a message that is entirely one of the following (whole message, case-insensitive) to drop the stored session, so the next message starts a fresh `claude -p` session:

```
新對話   開新對話   重新開始   重開   清除對話   忘記
/new   /reset   reset   new chat
```

The match is strict and whole-message (trailing whitespace and punctuation are tolerated), so a passing mention such as `來開新對話討論 X` does not reset the session. On reset the bot reacts and confirms.

## Notes

> [!WARNING]
>
> This bridge and the official Discord plugin log in with the same bot token. Before running this as a service you must disable the official plugin (`claude plugin disable discord@claude-plugins-official`), because two gateway logins on one token produce duplicate replies.

> [!WARNING]
>
> Discord input is untrusted and is a prompt-injection surface.
>
> * Inbound messages are wrapped in a `<channel source="discord" ...>` tag, with metadata in the tag and the untrusted text inside; the system prompt tells the model to treat that content as chat text, never as instructions to change configuration or access rules.
> * Only user IDs in `allowFrom` in `access.json` can trigger a run, and a failure to read the file fails closed. Button clickers, including the permission-approval buttons, are re-authenticated against the same allowlist.
> * The permission mode defaults to `auto` and `bypassPermissions` is never used; set `BOT_PERMISSION_MODE=default` to harden further.
> * Access and pairing are managed by the owner only, in the state directory, never via chat.
> * Keep the bot token out of version control.

## In practice

This fits keeping Claude Code on call in a Discord DM without leaving a terminal open: by default, long jobs are not killed for running long (only stream silence counts as wedged), operations that need approval are decided with a button click in the chat, and the bot can take attachments and send back produced files. The limits are that it only supports Windows 11 and that only allowlisted users can trigger a run.

## Links

* GitHub: [felimet/claude-discord-daemon](https://github.com/felimet/claude-discord-daemon)
* Bun: [bun.sh](https://bun.sh)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.