Skip to main content

Overview

The Slack channel uses Socket Mode with the Bolt framework to connect to Slack. No public URL or webhook endpoint is needed — the connection is initiated from your server.

Installation

Run the /add-slack skill in Claude Code:
/add-slack
The skill will:
  • Install the Slack channel code
  • Add required dependencies (@slack/bolt)
  • Guide you through Slack app creation
  • Configure authentication
  • Register your first channel

Creating a Slack App

1

Create Application

  1. Go to api.slack.com/apps
  2. Click Create New AppFrom scratch
  3. Enter app name (e.g., “Andy Assistant”) and select your workspace
2

Enable Socket Mode

  1. Go to SettingsSocket Mode
  2. Toggle Enable Socket Mode to ON
  3. Click Generate to create an App-Level Token
  4. Name it “Socket Mode Token” and add scope connections:write
  5. Copy the token (starts with xapp-) — this is your SLACK_APP_TOKEN
3

Subscribe to Events

  1. Go to Event Subscriptions
  2. Toggle Enable Events to ON
  3. Under Subscribe to bot events, add:
    • message.channels - Messages in public channels
    • message.groups - Messages in private channels
    • message.im - Direct messages
4

Add OAuth Scopes

  1. Go to OAuth & Permissions
  2. Under Bot Token Scopes, add:
    • chat:write - Send messages
    • channels:history - Read public channel messages
    • groups:history - Read private channel messages
    • im:history - Read direct messages
    • channels:read - View public channels
    • groups:read - View private channels
    • users:read - Read user display names
5

Install to Workspace

  1. Go to OAuth & Permissions
  2. Click Install to Workspace
  3. Review permissions and click Allow
  4. Copy the Bot User OAuth Token (starts with xoxb-) — this is your SLACK_BOT_TOKEN
For detailed setup with screenshots, see the SLACK_SETUP.md guide.

Configuration

Add both tokens to .env:
SLACK_BOT_TOKEN=xoxb-your-bot-token
SLACK_APP_TOKEN=xapp-your-app-token
Then sync to container environment:
mkdir -p data/env && cp .env data/env/env
Both tokens are required. The bot token authenticates API calls, while the app token enables Socket Mode connection.

Channel Registration

Getting the Channel ID

1

Add Bot to Channel

  1. Open the Slack channel
  2. Right-click channel name → View channel details
  3. Go to Integrations tab
  4. Click Add apps and select your bot
2

Copy Channel ID

Method 1: From URLOpen the channel in browser. The URL looks like:
https://app.slack.com/client/T0123456/C0123456789
The C... part is the channel ID.Method 2: Copy LinkRight-click the channel name → Copy link → extract the C... ID from the URLMethod 3: API
curl -s -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
  "https://slack.com/api/conversations.list" | \
  jq '.channels[] | {id, name}'

Registering a Main Channel

Main channels respond to all messages (no trigger required):
registerGroup("slack:C0123456789", {
  name: "#general",
  folder: "slack_main",
  trigger: "@Andy",
  added_at: new Date().toISOString(),
  requiresTrigger: false,
  isMain: true,
});

Registering Additional Channels

Additional channels require the trigger pattern:
registerGroup("slack:C9876543210", {
  name: "#dev-team",
  folder: "slack_dev-team",
  trigger: "@Andy",
  added_at: new Date().toISOString(),
  requiresTrigger: true,
});

How It Works

Connection

  1. Slack channel reads tokens from .env
  2. Creates Bolt app with Socket Mode enabled
  3. Connects via WebSocket (no public URL needed)
  4. Listens for message events

Message Handling

Thread Flattening
Threads are flattened into the main channel conversation. The agent sees threaded replies as regular messages but has no awareness they originated in a thread. Responses always go to the channel, not back into the thread.
Message Splitting Slack has a 4000-character limit per message. Long responses are automatically split:
const MAX_MESSAGE_LENGTH = 4000;

if (text.length > MAX_MESSAGE_LENGTH) {
  // Split into chunks and send sequentially
  for (let i = 0; i < text.length; i += MAX_MESSAGE_LENGTH) {
    const chunk = text.substring(i, i + MAX_MESSAGE_LENGTH);
    await this.app.client.chat.postMessage({ channel: jid, text: chunk });
  }
}
Splitting is naive (fixed 4000-char boundary). It may break mid-word or mid-sentence.
User Name Resolution
// Bot messages show as assistant name
if (isBotMessage) {
  senderName = ASSISTANT_NAME;
} else {
  // Fetch display name from Slack API (with caching)
  const userInfo = await this.app.client.users.info({ user: msg.user });
  senderName = userInfo.user?.profile?.display_name || 
               userInfo.user?.real_name || 
               msg.user;
}

Channel Metadata Sync

The channel syncs metadata for all channels the bot is a member of:
async syncChannelMetadata(): Promise<void> {
  let cursor: string | undefined;
  do {
    const result = await this.app.client.conversations.list({
      types: 'public_channel,private_channel,im',
      cursor,
      limit: 200,
    });
    // Update chat names in database
    for (const channel of result.channels || []) {
      const jid = `slack:${channel.id}`;
      updateChatName(jid, channel.name || jid);
    }
    cursor = result.response_metadata?.next_cursor;
  } while (cursor);
}

Troubleshooting

Bot Not Responding

1

Check Tokens

grep SLACK_BOT_TOKEN .env
grep SLACK_APP_TOKEN .env
grep SLACK_BOT_TOKEN data/env/env
grep SLACK_APP_TOKEN data/env/env
2

Check Registration

sqlite3 store/messages.db "SELECT * FROM registered_groups WHERE jid LIKE 'slack:%'"
3

Check Bot is Added

In Slack: Channel details → Integrations → verify your bot is listed
4

Check Service

# macOS
launchctl list | grep nanoclaw

# Linux
systemctl --user status nanoclaw

Bot Connected But Not Receiving Messages

  1. Verify Socket Mode is enabled: Slack app settings → Socket Mode → ON
  2. Verify event subscriptions: Should have message.channels, message.groups, message.im
  3. Verify bot is added to channel: Check Integrations tab in channel details
  4. Verify OAuth scopes: Should have channels:history and/or groups:history

”missing_scope” Errors

1

Add Missing Scope

Go to OAuth & Permissions in app settings and add the scope listed in the error
2

Reinstall App

Scope changes require reinstallation. Click Reinstall to Workspace
3

Update Token

The Bot Token changes on reinstall. Copy the new token and update .env
4

Sync and Restart

mkdir -p data/env && cp .env data/env/env
npm run build
launchctl kickstart -k gui/$(id -u)/com.nanoclaw

Implementation Details

Dependencies

  • @slack/bolt - Slack Bolt framework with Socket Mode support
  • @slack/types - TypeScript types for Slack API

JID Format

  • Public channels: slack:C0123456789
  • Private channels: slack:G0123456789
  • Direct messages: slack:D0123456789

Self-Registration Code

registerChannel('slack', (opts: ChannelOpts) => {
  const env = readEnvFile(['SLACK_BOT_TOKEN', 'SLACK_APP_TOKEN']);
  if (!env.SLACK_BOT_TOKEN || !env.SLACK_APP_TOKEN) return null;
  return new SlackChannel(opts);
});
The channel only activates if both SLACK_BOT_TOKEN and SLACK_APP_TOKEN are set.

Known Limitations

  • Threads are flattened — No thread-aware routing (requires database schema changes)
  • No typing indicator — Slack Bot API doesn’t expose a typing endpoint
  • Naive message splitting — Fixed 4000-char boundary may break mid-sentence
  • No file/image handling — Only text content is processed
  • Unbounded metadata sync — May be slow in workspaces with thousands of channels

Next Steps

Add Discord

Add Discord as another channel

Channel Overview

Learn about the channel system

Build docs developers (and LLMs) love