@chat-adapter/discord 4.26.0, pinned — the /add-discord skill may pin a slightly newer adapter version) and connects over Discord’s Gateway — a WebSocket NanoClaw opens outbound, with events forwarded to a local loopback server. No public URL, webhook, or open port needed; works behind NAT and on a laptop.
The bot works in server text channels, threads, and DMs.
Prerequisites
- A Discord account, and a server you can add the bot to (the wizard walks you through creating both the app and a server if needed)
- A working NanoClaw install (quickstart)
- A bot from the Developer Portal with Message Content Intent enabled (under Bot → Privileged Gateway Intents) — without it the bot connects but can’t read message text
- The bot token from the Bot tab. That’s the only thing you paste — the wizard derives the application ID and public key from the token via Discord’s API.
Install
Discord is offered in the first-run setup wizard, or add it later by running/add-discord in Claude Code. The wizard flow:
Create the bot (if you don't have one)
The wizard opens the Developer Portal: click New Application, then in the Bot tab click Reset Token, copy the token, and enable Message Content Intent.
Paste the token
The wizard format-validates it, calls
/users/@me to confirm Discord accepts it and resolve the bot’s username, then calls /oauth2/applications/@me to derive the application ID and public key. If a DISCORD_BOT_TOKEN already exists in .env, it offers to reuse it.Confirm your identity
The wizard reads the app owner from the API and asks “Is @you your Discord account?”. If the app is team-owned (or you decline), it falls back to a manual user-ID prompt with Developer Mode instructions.
Invite the bot to a server
The bot can’t DM you until you share a server. The wizard generates and opens the OAuth invite URL (scope
bot, permissions 100416: Send Messages, Read Message History, Add Reactions, Attach Files) — pick any server, a personal one is fine.Adapter install
setup/add-discord.sh copies the adapter from the channels branch, installs the pinned package, builds, writes DISCORD_BOT_TOKEN, DISCORD_APPLICATION_ID, and DISCORD_PUBLIC_KEY to .env (synced to data/env/env), and restarts the service./manage-channels. Server channels are identified as discord:<guildId>:<channelId> (enable Developer Mode in Discord, then right-click the server for the guild ID and the channel for the channel ID); DMs as discord:@me:<dmChannelId>.
Platform notes
- Gateway connection with backoff — the bridge starts a Gateway listener in 24-hour cycles and forwards raw events (including button clicks) to a local HTTP server bound to
127.0.0.1. If the listener dies, it reconnects with exponential backoff (1s doubling per consecutive failure, capped at 1 hour); a run lasting over 5 minutes counts as healthy and resets the counter. The cap exists because an unrecoverable failure like a bad token would otherwise restart ~10×/sec and get the IP blocked by Discord’s Cloudflare layer. - Threads — the adapter sets
supportsThreads: true, and on server channels the router gives every thread its own agent session automatically: even wirings set tosharedbehave asper-threadhere. Onlyagent-shared(one session across all of an agent’s messaging groups) overrides this. In DMs the forcing doesn’t apply:sharedwirings get a single session regardless of sub-threads, while an explicitper-threadwiring is honored — see the entity model. - Mentions and engagement — @mentioning the bot in an unwired channel is platform-confirmed and reaches the router as a mention; in mention-sticky wirings the bot then sticks to that thread (plain mention wirings respond per mention without subscribing). DMs are always treated as addressed to the bot.
- Replies and attachments — replying to a message passes the quoted text and sender (display name or username) to the agent as context. Attachments are downloaded and passed to the agent as data, not just described.
- Interactive questions — when an agent asks a multiple-choice question, it renders as an embed with buttons; your click arrives via the Gateway and the card updates in place to show the selection.
- 2,000-character limit — Discord caps messages at 2,000 characters. The current adapter doesn’t enable the bridge’s automatic chunking, so longer replies can be truncated by the platform.
- Typing indicator — the bot shows a typing indicator while the agent works.
Troubleshooting
- “Discord didn’t accept that token” —
/users/@merejected it. Tokens are only shown once in the Developer Portal; click Reset Token and copy the fresh one (the old one stops working). “Couldn’t reach Discord” instead means a network problem — check connectivity and retry setup. - Bot connects but never responds to text — Message Content Intent isn’t enabled. Developer Portal → your app → Bot → Privileged Gateway Intents → enable it, then restart the service.
- “Couldn’t open a DM channel with you” / no welcome message — the bot and your account don’t share a server yet. Open the invite URL, add the bot to any server you’re in, then retry with
/manage-channels. Gateway listener error, retryingrepeating in logs — the listener is in its backoff loop (the log line includesconsecutiveFailuresand the nextdelayMs). Growing delays usually mean an unrecoverable cause like an invalid token — fix the credential rather than waiting out the retry.