@chat-adapter/slack 4.26.0, pinned) and receives events over webhooks: Slack POSTs to /webhook/slack on NanoClaw’s shared webhook server. Unlike Discord or Telegram, that means Slack needs a public URL that reaches your machine.
The bot works in public channels, private channels, DMs, and threads.
Prerequisites
- A Slack workspace where you can install apps
- A working NanoClaw install (quickstart)
- A Slack app created From scratch at api.slack.com/apps with these Bot Token Scopes (under OAuth & Permissions):
chat:write,im:write,im:history,channels:read,channels:history,groups:read,groups:history,users:read,reactions:write,files:read,files:write - App Home → enable the Messages Tab and check “Allow users to send slash commands and messages from the messages tab” — without this you can’t DM the bot
- Two credentials to paste: the Bot User OAuth Token (
xoxb-…, shown after Install to Workspace) and the Signing Secret (under Basic Information). No app-level token — v2 doesn’t use Socket Mode. - A way to expose port 3000 publicly — ngrok, Cloudflare Tunnel, or a reverse proxy on a VPS
Install
Slack is offered in the first-run setup wizard, or add it later by running/add-slack in Claude Code. The wizard flow:
Create the Slack app
The wizard opens api.slack.com/apps and walks you through the app creation checklist above: scopes, Messages Tab, signing secret, and workspace install.
Paste the bot token and signing secret
Both are password prompts. The wizard format-validates them (
xoxb- prefix, hex secret), then calls auth.test to confirm Slack accepts the token and resolve your workspace and bot identity. If SLACK_BOT_TOKEN or SLACK_SIGNING_SECRET already exist in .env, it offers to reuse them.Adapter install
setup/add-slack.sh copies the adapter from the channels branch, installs the pinned package, builds, writes SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET to .env (synced to data/env/env), and restarts the service.Identify yourself
The wizard asks for your Slack member ID (
U… — in Slack, click your profile picture → Profile → ⋮ → Copy member ID), then calls conversations.open to get a DM channel with you.Name the agent and get the welcome DM
The wizard asks for your operator role and an agent name (default
Nano), wires the DM to your first agent group, and sends a welcome message. The DM is delivered outbound via chat.postMessage, so it arrives even before webhooks are configured — but the bot can’t hear your replies yet.Expose the webhook server and finish in Slack
Make port 3000 publicly reachable (ngrok, Cloudflare Tunnel, or a reverse proxy), then in your Slack app:
- Event Subscriptions → enable, set the Request URL to
https://<your-public-host>/webhook/slack(Slack sends a verification challenge that must pass), and subscribe to the bot eventsmessage.channels,message.groups,message.im, andapp_mention - Interactivity & Shortcuts → enable, same Request URL
- Reinstall the app when Slack prompts you to apply the new settings
/manage-channels. Channels are identified as slack:<channelId> (right-click the channel name → View channel details — the ID starts with C and sits at the bottom); DMs as slack:<dmId> (starts with D). Add the bot to a channel before wiring it.
Platform notes
- Webhook delivery — the bridge registers Slack on the shared webhook server (port 3000, configurable via
WEBHOOK_PORT), which routes/webhook/slackto the adapter. Requests are authenticated with your signing secret. The public URL must stay reachable — if your tunnel dies, the bot silently stops hearing messages. - Threads — the adapter sets
supportsThreads: true, so in group channels the router forcesper-threadsessions: each Slack thread gets its own agent session (unless the wiring usesagent-shared, which keeps one session across all of an agent’s messaging groups). DMs collapse sub-threads into one session. See the entity model. - Mentions and engagement — @mentioning the bot in an unwired channel reaches the router as a platform-confirmed mention (the
app_mentionevent); 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. - Interactive questions — when an agent asks a multiple-choice question, it renders as a card with buttons. Clicks arrive through the Interactivity request URL (same
/webhook/slackroute) and the card updates in place to show the selection. If buttons do nothing, Interactivity isn’t configured. - Attachments and reactions — file uploads are downloaded and passed to the agent as data (
files:read), and the agent can react to messages (reactions:write). - Formatting — Slack uses mrkdwn, not standard Markdown (
*bold*not**bold**,<url|text>links, no headings). Theslack-formattingcontainer skill is mounted into agent containers with a full mrkdwn reference. - No outbound chunking — the adapter doesn’t set the bridge’s
maxTextLength, so long replies are posted as a single message. Slack’s message limit is high enough that this rarely matters.
Troubleshooting
- “Slack didn’t accept that token” —
auth.testrejected it (invalid_authortoken_revoked). Copy the token again from OAuth & Permissions and retry setup. “Couldn’t reach Slack” instead means a network problem. - Welcome DM arrived but the bot never replies — outbound works without webhooks; inbound doesn’t. Check that your public URL is up, Event Subscriptions is enabled with a verified Request URL, and the bot events are subscribed. Slack’s URL verification fails if the signing secret in
.envdoesn’t match the app. missing_scopewhen opening the DM — your app lacksim:write. Add it under OAuth & Permissions, reinstall the app to the workspace, then retry setup.- Webhook returns 404
Unknown adapter: slack— the adapter never registered, usually becauseSLACK_BOT_TOKENis missing from the environment the service reads (the factory returnsnullwithout it). Verify.envanddata/env/env, then restart the service.