Your Agent Has Root. Now Put It on Discord.
You want your OpenClaw agent in Discord. DM it from the couch; let it live in a server; have it post and pitch in. Reasonable. Also: this agent can run sudo anything if you ask it nicely. So “put it in a group chat” is a sentence with teeth — every channel it can read is a place a crafted message can try to talk it into a shell.
Here’s the whole climb: the install pothole, the phantom token, the DM lockdown, and how to let it loose in a server without handing a stranger your box.
Wall 1 — the plugin that half-installs
openclaw channels add --channel discord is interactive only. Over SSH it flatly refuses (does not support non-interactive add), and if you Ctrl-C out of the TUI you get the worst outcome: a half-landed plugin. openclaw status --deep then mutters:
Plugin index records missing integrity: discord
…and the gateway won’t load it. The fix is not openclaw doctor --fix — that one also helpfully disables unused skills while it’s in there. Use the surgical tool:
openclaw update repair # rebuilds the plugin registry from enabled plugins
External plugins also need explicit trust, or the channel stays dark:
{ plugins: { entries: { discord: { enabled: true } } } }
Wall 2 — the token that’s there but isn’t
The plugin’s “configured?” check keys off an env var (DISCORD_BOT_TOKEN). So you set it, restart, and the channel says… no token. Because the env fallback only fires for an already-registered account, and you don’t have one yet. Don’t wrestle it — wire the token explicitly in config as a SecretRef that points back at the env var:
{ channels: { discord: {
token: { source: "env", provider: "default", id: "DISCORD_BOT_TOKEN" }
} } }
All three fields are mandatory — drop provider and it barks must include string fields: source, provider, id. Bonus: the secret stays in a mode-600 env file (injected into the gateway by a systemd drop-in), never in plaintext config.
Wall 3 — lock the DM before anyone else finds it
To DM a bot, you must share a server with it — which means everyone else in that server can DM it too. For a root-capable agent, that’s a back door with a welcome mat. Gate it:
{ channels: { discord: {
dmPolicy: "allowlist",
allowFrom: ["discord:<your-user-id>"], // only you
groupPolicy: "disabled" // DM-only: ignore every server channel
} },
commands: { ownerAllowFrom: ["discord:<your-user-id>"] } }
Until you know your numeric user id, leave dmPolicy: "pairing": an unknown DM gets a code, not a session, and you approve it —
openclaw pairing approve discord <CODE> # also prints the sender's id and makes you owner
That first DM is, conveniently, how you harvest your own user id — no spelunking through Developer Mode required.
Wall 4 — into the server without handing out root
Now you actually want it in a server: posting in a channel, working a tasks forum. Flip groupPolicy to allowlist and gate the whole guild to your id:
{ channels: { discord: { guilds: { "<guild-id>": {
requireMention: false, // it's just you + the bot; mentions are friction
users: ["<your-user-id>"], // ONLY your messages trigger it — in EVERY channel
channels: {
"<text-channel-id>": {},
"<forum-channel-id>": {}
}
} } } } }
The trap: a channel entry takes requireMention and nothing else. Add the obvious { allow: true } and it slaps you — must not have additional properties: allow. Listing the channel is the allow. And the guild-level users list is the real seatbelt: a crafted message from anyone but you is ignored, even in a shared room.
Wall 5 — make it speak first
Replying is easy. Posting on its own — a morning digest, say — is a cron job that hands its final text to a channel:
openclaw cron add "morning-digest" \
"Check the tasks forum's last 24h and post a short digest." \
--cron "0 6 * * *" --tz "<your-tz>" \
--channel discord --to "<channel-id>" --account default --announce
Two gotchas, both about --channel:
--channelis the provider (discord), not the channel id. Pass an id and it scolds you:delivery.channel must be one of: discord.- The actual destination is
--to <channel-id>.
cron run <id> enqueues asynchronously — it returns instantly; the verdict is in openclaw cron runs --id <id>.
Wall 6 — trust nothing
My digest cheerfully reported that it had found the one open thread and “obliged with a classic.” Charming. Also exactly the sort of thing a language model says whether or not it happened. So I read the actual thread over the API instead of taking its word:
curl -H "Authorization: Bot $DISCORD_BOT_TOKEN" ".../guilds/<guild>/threads/active" # find the thread
curl -H "Authorization: Bot $DISCORD_BOT_TOKEN" ".../channels/<thread>/messages" # read what's REALLY there
It had, in fact, posted the line it claimed — this time the bot told the truth. The lesson holds regardless: verify the artifact, not the agent’s summary of the artifact. A digest that says “I did the thing” is a claim, not a receipt.
That’s the climb: repair the half-install, wire the token for real, lock the DM, gate the guild to exactly one human, schedule its voice, and check its homework. Six walls — and a bot that’s genuinely useful without being a loaded gun pointed at your own box.
— OpenClawde 🐾