> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nanoclaw.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Discord

> Connect NanoClaw to a Discord bot via the Chat SDK adapter — Developer Portal app, Gateway connection, and per-thread sessions.

The Discord adapter connects NanoClaw to a bot you create in the Discord Developer Portal. It's built on the Chat SDK bridge (`@chat-adapter/discord` 4.29.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](/quickstart))
* A bot from the [Developer Portal](https://discord.com/developers/applications) 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. Both paths run the same `/add-discord` skill:

<Steps>
  <Step title="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**.
  </Step>

  <Step title="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, the public key, and your Discord identity (the app owner). If a `DISCORD_BOT_TOKEN` already exists in `.env`, it offers to reuse it.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Adapter install">
    The skill copies `src/channels/discord.ts` from the `channels` branch, registers it in the channel barrel, installs the pinned `@chat-adapter/discord` package, builds and tests, writes `DISCORD_BOT_TOKEN`, `DISCORD_APPLICATION_ID`, and `DISCORD_PUBLIC_KEY` to `.env` (synced to `data/env/env`), and restarts the service.
  </Step>

  <Step title="Name the agent and get the welcome DM">
    The wizard opens a DM channel with you via the API, asks for your operator role and an agent name (default `Nano`), wires the DM to your first agent group, and sends a welcome message.
  </Step>
</Steps>

To wire server channels or more DMs later, run `/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 supports threads and declares `threads: true` as the channel default for server channels, so the router gives every thread its own agent session automatically: even wirings set to `shared` behave as `per-thread` here. Only `agent-shared` (one session across all of an agent's messaging groups) overrides this, and setting the wiring's [`--threads false`](/reference/ncl-cli#wirings) collapses the channel back to one session across threads. DMs declare `threads: false`: sub-threads collapse into the single DM session — even for `per-thread` wirings — unless the wiring sets `--threads true`. See the [entity model](/concepts/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, forwards, and attachments** — replying to a message passes the quoted text and sender (display name or username) to the agent as context. Forwarded messages are unwrapped: Discord ships their content in a snapshot rather than the message body, and the adapter restores the text (tagged `[Forwarded message]`) so the agent sees it. 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 adapter sets the bridge's `maxTextLength` to 2,000, so longer agent replies are split into multiple messages instead of failing.
* **Typing indicator** — the bot shows a typing indicator while the agent works.

## Troubleshooting

* **"Discord didn't accept that token"** — `/users/@me` rejected 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, retrying` repeating in logs** — the listener is in its backoff loop (the log line includes `consecutiveFailures` and the next `delayMs`). Growing delays usually mean an unrecoverable cause like an invalid token — fix the credential rather than waiting out the retry.

For service-level checks (logs, restarts, wiring queries), see [troubleshooting](/operate/troubleshooting).
