Once Hermes is running as a gateway, Slack is the next useful surface.

The good news: Hermes supports modern Slack apps through Socket Mode. That means you do not need to expose a public Slack events endpoint. The Hermes process opens a WebSocket connection to Slack, and Slack sends messages through that connection.

The less-good news: Slack app setup has enough switches that one missing scope or event subscription can make the bot look half-alive.

Here is the setup I would repeat.


The moving pieces

A Hermes Slack integration needs three things:

  1. a Slack bot token,
  2. a Slack app-level Socket Mode token,
  3. Hermes config/env vars pointing at both.

The token shapes are:

SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...

Hermes also needs an allowlist:

SLACK_ALLOWED_USERS=U...

Use Slack member IDs, not display names.


Generate the Hermes Slack manifest

Hermes can generate a Slack app manifest for you. Use it.

Inside the Hermes container:

hermes slack manifest --write

That writes the manifest to the Hermes data directory:

/opt/data/slack-manifest.json

You can print it with:

cat /opt/data/slack-manifest.json

Then create the Slack app:

  1. open https://api.slack.com/apps,
  2. click “Create New App”,
  3. choose “From an app manifest”,
  4. pick the workspace,
  5. paste the manifest JSON,
  6. create the app.

The manifest handles most of the tedious setup: scopes, Socket Mode, event subscriptions, and slash commands.

If Slack rejects the manifest, inspect the error rather than guessing. Slack is strict about command names and app configuration.


Required Slack scopes and events

If you configure manually, the important bot scopes are:

chat:write
app_mentions:read
channels:history
channels:read
groups:history
im:history
im:read
im:write
users:read
files:read
files:write

The most commonly missed ones are:

channels:history
groups:history
files:read

Without channel history events, the bot may work in DMs but not in channels. Without file read access, attachments will be unreliable.

The important bot events are:

message.im
message.channels
message.groups
app_mention

If the app can DM but does not see channel messages, check event subscriptions first.


Enable Socket Mode

In the Slack app settings:

  1. go to “Socket Mode”,
  2. enable Socket Mode,
  3. create an app-level token,
  4. give it connections:write,
  5. copy the xapp-... token.

That token becomes:

SLACK_APP_TOKEN=xapp-...

This is what lets Hermes connect to Slack without a public webhook endpoint.


Enable the Slack app messages tab

This is an easy one to miss.

In Slack app settings:

  1. go to “App Home”,
  2. enable the Messages Tab,
  3. enable “Allow users to send Slash commands and messages from the messages tab”.

If this is off, users may see a Slack-side message saying they cannot DM the app. That is not a Hermes bug. It is a Slack app setting.


Install the app and collect tokens

After the app is created and configured, install it to the workspace.

Slack will give you a bot token:

SLACK_BOT_TOKEN=xoxb-...

You should already have the app-level token:

SLACK_APP_TOKEN=xapp-...

Then add these to the CapRover Hermes app environment:

SLACK_BOT_TOKEN=xoxb-...
SLACK_APP_TOKEN=xapp-...
SLACK_ALLOWED_USERS=U094404ABFW
SLACK_HOME_CHANNEL=C094404JJCU
SLACK_HOME_CHANNEL_NAME=general

The exact IDs will be different for your workspace. The key point is that SLACK_ALLOWED_USERS must be Slack member IDs.

After changing env vars, restart or redeploy the Hermes app.


Invite the bot to channels

Slack bots do not automatically join every channel.

In a channel where you want Hermes to respond:

/invite @Hermes

In DMs, Hermes should respond to normal messages.

In channels, Hermes generally responds when mentioned:

@Hermes are you live?

In threads, once Hermes is participating, it can continue the thread naturally depending on the gateway behavior.


Verify the gateway side

The Hermes logs should show clean gateway startup:

Hermes Gateway Starting...

If Slack is configured but Hermes does not respond, check logs while sending a fresh message.

There are three broad classes of failure.

1) Slack never reaches Hermes

Likely causes:

  • wrong SLACK_APP_TOKEN,
  • Socket Mode disabled,
  • missing connections:write,
  • app not restarted after env changes.

2) Hermes sees the message but rejects the user

Likely cause:

SLACK_ALLOWED_USERS

Make sure it contains the sender’s Slack member ID, not their username.

3) Hermes receives the message but model inference fails

This looks like a Slack problem, but it is usually provider config.

Examples:

No inference provider configured
No Codex credentials stored
Permission denied: /opt/data/auth.json

Those are not Slack failures. They mean Hermes got the Slack event and then failed when trying to answer.

For a CapRover deployment, the permission-denied version usually means someone ran hermes model or hermes auth as root inside the container, and the gateway user can no longer read /opt/data/auth.json or /opt/data/config.yaml.

Fix file ownership/permissions on /opt/data, then restart the gateway.


Slash commands

The generated manifest registers Hermes commands as Slack slash commands.

After updates, regenerate and re-apply the manifest:

hermes slack manifest --write
cat /opt/data/slack-manifest.json

Then in Slack app settings:

  1. go to “App Manifest”,
  2. paste the updated manifest,
  3. save,
  4. reinstall the app if Slack asks.

Slack slash commands do not work inside thread replies. Hermes supports a ! prefix workaround for commands in threads, so a command like this can work where /model cannot:

!model

My final checklist

Before calling Slack done, I want these checks to pass:

  1. app manifest applied,
  2. Socket Mode enabled,
  3. bot token and app token added to Hermes env,
  4. SLACK_ALLOWED_USERS contains the real Slack member ID,
  5. app restarted,
  6. bot invited to the target channel,
  7. DM test works,
  8. channel mention works,
  9. Hermes logs show no provider/auth errors.

A good final test is simple:

Reply with exactly: slack-ok

If Hermes replies with slack-ok, Slack transport and model inference are both working.

That distinction matters. A bot that connects to Slack but cannot call its model is not done. A model that works in the container but fails through Slack is not done either.

The integration is done when a real Slack message produces a real model-backed reply.