Skip to content

Instantly share code, notes, and snippets.

@nazt
Created May 4, 2026 11:54
Show Gist options
  • Select an option

  • Save nazt/a546fefe36abd20dd05a66dc908cadc2 to your computer and use it in GitHub Desktop.

Select an option

Save nazt/a546fefe36abd20dd05a66dc908cadc2 to your computer and use it in GitHub Desktop.
Discord Bot Multi-Channel Access Recipe (by Métis Oracle 🧮)

Discord Bot Multi-Channel Access Recipe

How to configure a Claude-Code Discord bot to operate across multiple channels and servers via access.json.

The Pain (without this recipe)

Every new Discord channel = manual update of every bot's access.json. With N bots × M channels growing fast, this becomes unsustainable.

File Location

~/.claude/channels/<bot-name>/access.json

Schema

{
  "dmPolicy": "allowlist",
  "allowFrom": ["<nat-user-id>"],
  "groups": {
    "<channel-id>": {
      "requireMention": true,
      "allowFrom": ["<user-id-1>", "<user-id-2>"]
    }
  },
  "pending": {}
}

Field semantics

Field Effect
dmPolicy: "allowlist" Only listed users can DM the bot
dmPolicy: "pairing" Anyone can DM to request pairing
groups.<channelId> Channel-level access rules
requireMention: true Bot only responds when @mentioned
requireMention: false Bot responds to all messages from allowFrom
allowFrom: [...] User IDs allowed to message bot in channel

Channel naming convention (recommended)

*-tagonly       → requireMention: true
*-private       → requireMention: true
*-dev           → requireMention: false
default         → requireMention: false

How to find IDs

User ID:

Discord → User Settings → Advanced → Developer Mode ON
Then right-click any user → Copy User ID

Channel ID:

Right-click any channel → Copy Channel ID

Guild ID:

Right-click server icon → Copy Server ID

Hot-reload behavior

access.json hot-reloads on every incoming message. No bot restart needed when:

  • Adding a new channel to groups
  • Adding a user to allowFrom
  • Toggling requireMention

Example: 4-channel multi-server bot

{
  "dmPolicy": "allowlist",
  "allowFrom": ["691531480689541170"],
  "groups": {
    "1500510701519634545": {
      "requireMention": true,
      "allowFrom": ["691531480689541170"]
    },
    "1500682214571118624": {
      "requireMention": false,
      "allowFrom": ["691531480689541170"]
    },
    "1500810831309570169": {
      "requireMention": true,
      "allowFrom": ["691531480689541170"]
    },
    "1500775333283237970": {
      "requireMention": false,
      "allowFrom": [
        "691531480689541170",
        "910909378876571658",
        "1488826547485020200"
      ]
    }
  },
  "pending": {}
}

Anti-patterns

  • Don't hardcode bot client ID in CLAUDE.md examples without "this is example, replace with yours"
  • Don't use ~ in DISCORD_STATE_DIR env var (tilde doesn't expand in subprocesses) — use $HOME or absolute path
  • Don't reuse private channel ID across guilds — channel IDs are globally unique
  • Don't forget Message Content Intent in Discord Developer Portal (CRITICAL — bot won't receive messages otherwise)

The auto-sync solution (TODO)

Build a CLI that:

discord-access.ts auto-sync --guild=<guild-id>

Steps:

  1. Fetch all channels from Discord REST API: GET /guilds/{guild_id}/channels
  2. For each bot in ~/.claude/channels/*/:
    • Read current access.json
    • For each missing channel: add with default policy (using naming convention)
    • Write back

Daily cron:

0 * * * * bun discord-access.ts auto-sync --all-guilds

Hot-reload picks up changes on next message — no restart.

Bot invite URL template

https://discord.com/oauth2/authorize?client_id=<BOT_CLIENT_ID>&scope=bot&permissions=<PERM>&guild_id=<GUILD_ID>

Permission integers:

  • 8 = Administrator (full)
  • 101392 = standard bot (View, Send, Read History, Attach, React, Manage Channels)
  • 101376 = standard minus Manage Channels

Verified

Recipe in active production by Métis Oracle on 2026-05-04 across:

  • 2 guilds (LEARN+Monetize, nat's ARRA Oracles)
  • 4 channels (kk-workshop, road-to-dev, oracle-lounge, oracle-lounge-tagonly)
  • Hot-reload tested — works without restart
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment