Files
ironclaw/docs/channels/slack.mdx
BenKurrek c63456cd00 docs(replies): align guidance, comments, and test docs with the reply seam
Guidance and docs now describe what ships: every declared [channel.reply]
binds one ReplySink (stream or message is cadence only), the session
channel's sink is the projection sink composition attaches, final-reply
attachments materialize inside reply publication, and the two 2026-08
channel design records carry superseded-in-part banners pointing at the
progressive-reply design. The Slack rows say stream replies; the unused
agents.sessions.rename link is gone from the setup docs; tests/AGENTS.md
maps the SSE first-text journeys.

Code-adjacent fixes riding along: the RebornRuntime delivery-coordinator
accessor is test-support again (production wiring takes the coordinator
from the factory) with the struct ratchet re-pinned 267 -> 268 / 43 -> 44
against main's 269 / 44; the binary's session-reply-channel naming is
pinned by a CLI test; the DCR recovery diagnostics are debug-level and
the malformed-record renewal has a caller-level test (sabotage-verified);
the stale notice-route comment, the split doc block in composition
test-support, the misplaced doc on the failure-notice test, the fake
channel adapter counters' docs, and the ExtensionBindings doc are
corrected; the Slack exactly-once integration journey waits for the
attempt to settle before sampling the wire.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 02:59:22 -04:00

408 lines
17 KiB
Plaintext

---
title: "Slack"
description: "Talk to your agent in Slack channels and direct messages"
icon: slack
---
Connect IronClaw to a Slack workspace so your agent can read and reply to messages in
channels and DMs.
## Prerequisites
- A Slack workspace where you can install apps
- An IronClaw instance reachable from Slack over HTTPS
---
## Setup
<Steps>
<Step title="Create a Slack app">
Create an app at [api.slack.com/apps](https://api.slack.com/apps). The fastest route is
**From an app manifest** — paste the [manifest below](#app-manifest), which enables the
**Agents** feature and sets the scopes, events, the `/ironclaw` slash command, and
redirect URL in one step. Replace `example.com` with your own host first.
If you build the app from scratch instead, enable **Agents** in the app's sidebar (this is
what the manifest's `features.agent_view` block does — see [Native Agent](#native-agent)),
then configure **OAuth & Permissions**, **Event Subscriptions**, **Slash Commands**, and
the redirect URL to match that manifest.
Once created, install the app to your workspace and copy the bot token from **OAuth &
Permissions**, and the signing secret from **Basic Information**. IronClaw uses the signing
secret to verify that incoming requests really came from Slack.
</Step>
<Step title="Start IronClaw">
```bash
ironclaw serve
```
</Step>
<Step title="Enter the deployment configuration (operator, once per instance)">
In the [web interface](/using/webui), open **Admin → Configuration** and fill in the
**Slack deployment configuration** card. Every field is required; the secrets are
written to the encrypted secret store and never shown again.
| Field | Where it comes from |
| --- | --- |
| Bot token | **OAuth & Permissions** → Bot User OAuth Token (`xoxb-…`) |
| Signing secret | **Basic Information** → App Credentials |
| Workspace (team) ID | Your workspace id (`T…`) — also the `team_id` in every Events API payload |
| App ID | **Basic Information** → App ID (`A…`) |
| Installation ID | A stable label you choose for this app/workspace installation, e.g. `slack-acme` |
| Bot user ID | The bot's member id (`U…`), shown under **App Home** or returned by `auth.test` |
| OAuth client ID | **Basic Information** → App Credentials |
| OAuth client secret | **Basic Information** → App Credentials |
The OAuth client id and secret drive the per-user ("personal") authorization described
below; the rest configure the workspace bot.
<Note>
The agent can't do this operator step for you — registering the app credentials stays in
the web interface. Once it's done, though, asking the agent to "connect Slack" does work
for the personal half: it installs and activates the extension, opens the in-chat
connection panel if your personal OAuth is missing, and confirms when your account is
already connected.
</Note>
</Step>
<Step title="Point Slack at your instance">
Back in your Slack app, confirm **Event Subscriptions** points at your instance:
```
https://your-host/webhooks/extensions/slack/events
```
Slack sends a verification challenge to that URL, so IronClaw must already be running and
reachable when you save it. Reinstall the app if Slack asks you to.
Also register the `/ironclaw` slash command under **Slash Commands**, pointed at the
identical Request URL — Slack allows a slash command's endpoint to be any URL, so one
signed address answers both surfaces. Registering a slash command adds the `commands` bot
scope, which also needs a reinstall.
</Step>
<Step title="Talk to it">
Pick IronClaw from Slack's **Agents** picker (or open its Messages tab) and start a chat,
invite the bot to a channel and mention it, or send it a direct message. In a channel the
bot only responds to messages that mention it; in a DM it responds to everything. The
answer streams into the thread as the run produces it, each tool call shows up as a task
card, and Slack's **Stop** button cancels the run.
</Step>
</Steps>
---
## Native Agent
IronClaw replies on Slack's native Agent surface rather than by posting a finished
message. Each run is one agent session in the conversation's thread: the session shows
*processing* while the run works and *suspended* while it waits on you (an approval, a
sign-in), the answer text streams in as it is produced, tool calls appear as task cards,
and the session returns to *active* when the run ends. This is what the manifest's
`features.agent_view` block, the `assistant:write` bot scope, and the Agent event
subscriptions are for.
<Warning>
Switching an app to `agent_view` is a one-way door. Slack's migration guide is explicit:
"Once you change your app's manifest from `assistant_view` to `agent_view`, you can't
revert to the Assistant messaging experience." Create the app from the manifest above,
or switch an existing app knowingly.
</Warning>
- **Agents picker.** With the Agents feature enabled, IronClaw is listed in Slack's Agents
picker and its Messages tab behaves like a chat with sessions; each new chat there is
a thread. `agent_description` (300 characters at most) and `suggested_prompts` are what
people see before their first message — edit them in the manifest to taste.
- **Stop button.** It appears while a session is processing because the app subscribes to
`agent_session_stopped`. Pressing it is normalized into the channel's `stop` command,
the run is cancelled, and IronClaw transitions the session out of *processing* itself
(Slack does not do that automatically).
- **If the Agent feature is missing.** There is no conventional-message fallback. A
workspace whose app was installed without the Agents feature answers `feature_disabled`
to `agents.sessions.setStatus`, and the reply fails clearly — the run's delivery is
recorded as failed with a reason naming `features.agent_view` — until an operator
enables **Agents** in the app settings and reinstalls the app. The same applies to a
bot token that lacks `chat:write` (`missing_scope`): the session and streaming methods
(`agents.sessions.setStatus`, `chat.startStream`,
`chat.appendStream`, `chat.stopStream`) all require it, and `assistant:write` is added
automatically when the feature is enabled.
- **Channels.** Streaming into a channel needs the person's user and team ids
(`recipient_user_id`/`recipient_team_id`), which IronClaw records with every inbound
message; a DM does not.
---
## URLs Slack Needs
Both are served by `ironclaw serve`, so they use whatever host and port your instance is
reachable on. Replace `your-host` and use HTTPS — Slack will not deliver to a plain-HTTP
or self-signed endpoint.
| Slack setting | URL |
| --- | --- |
| Event Subscriptions → Request URL | `https://your-host/webhooks/extensions/slack/events` |
| Slash Commands → Request URL | `https://your-host/webhooks/extensions/slack/events` |
| OAuth & Permissions → Redirect URL | `https://your-host/api/reborn/product-auth/oauth/slack/callback` |
<Note>
Event Subscriptions and the `/ironclaw` slash command point at the identical Request
URL — one signed endpoint answers both surfaces, so there is no second address to stand
up.
</Note>
The redirect URL is used for per-user ("personal") Slack authorization. IronClaw derives
it from the instance's own public base URL, so there is nothing to set on this side — just
register the value above in the Slack app.
<Warning>
The redirect URL must match **exactly** on both sides, including scheme, host, port, and
path. Slack rejects the authorization with a redirect-URI mismatch if it differs by even a
trailing slash. The path is `/api/reborn/product-auth/oauth/slack/callback` — a shortened
form like `/oauth/slack/callback` is not served and will 404.
</Warning>
For a local instance the values are `http://127.0.0.1:3000/...`, but Slack cannot reach
loopback, so events only work once the instance is reachable from the internet.
---
## App Manifest
Create the app with **From an app manifest** and paste this, replacing `example.com` with
your host. It enables the Agents feature (`features.agent_view`), and sets the scopes, bot
events, the `/ironclaw` slash command, and redirect URL that the steps above describe.
The canonical copy of this manifest ships with the Slack package as
`crates/extensions/packages/slack/app_manifest.json`; the block below is
test-pinned to be identical to that file, so either can be imported.
```json
{
"display_information": {
"name": "IronClaw"
},
"features": {
"agent_view": {
"agent_description": "IronClaw is your autonomous assistant: ask it to research, draft, run tools, and act across your connected apps. Replies stream into the thread as they are produced, with each step shown as it happens.",
"suggested_prompts": [
{
"title": "Summarize a thread",
"message": "Summarize the discussion in the thread I share next and list the open questions."
},
{
"title": "Draft a reply",
"message": "Draft a concise reply to the last message I received, in my voice."
},
{
"title": "Check my integrations",
"message": "Which integrations are connected for me right now, and what can you do with them?"
}
]
},
"app_home": {
"home_tab_enabled": false,
"messages_tab_enabled": true,
"messages_tab_read_only_enabled": false
},
"bot_user": {
"display_name": "IronClaw",
"always_online": true
},
"slash_commands": [
{
"command": "/ironclaw",
"url": "https://example.com/webhooks/extensions/slack/events",
"description": "Run IronClaw commands",
"usage_hint": "status | model use <name> | new | stop",
"should_escape": false
}
]
},
"oauth_config": {
"redirect_urls": [
"https://example.com/api/reborn/product-auth/oauth/slack/callback"
],
"scopes": {
"user": [
"search:read",
"channels:history",
"groups:history",
"im:history",
"mpim:history",
"channels:read",
"groups:read",
"im:read",
"mpim:read",
"users:read",
"chat:write",
"reactions:read",
"reactions:write",
"im:write"
],
"bot": [
"assistant:write",
"channels:history",
"app_mentions:read",
"chat:write",
"im:history",
"im:write",
"files:read",
"files:write",
"groups:history",
"mpim:history",
"reactions:write",
"commands"
]
},
"pkce_enabled": false
},
"settings": {
"event_subscriptions": {
"request_url": "https://example.com/webhooks/extensions/slack/events",
"bot_events": [
"app_mention",
"message.channels",
"message.groups",
"message.im",
"message.mpim",
"app_home_opened",
"app_context_changed",
"agent_session_stopped",
"agent_session_title_changed"
]
},
"org_deploy_enabled": false,
"socket_mode_enabled": false,
"token_rotation_enabled": false,
"is_mcp_enabled": false
}
}
```
<Note>
This is a starting point, not a minimum. The `user` scopes exist for per-user
authorization — searching and posting as the person rather than as the bot. If you only
want the bot to participate in channels, you can drop the `user` block and the redirect
URL with it. The `agent_view` block, `assistant:write`, `chat:write`, and the Agent
events (`app_home_opened`, `app_context_changed`, `agent_session_stopped`,
`agent_session_title_changed`) are not optional: they are how replies reach Slack.
(The legacy `assistant_thread_*` events are deliberately absent — Slack's Agent
View validator rejects manifests that subscribe to them.)
</Note>
`assistant:write` is added by Slack when the Agents feature is enabled and is declared
here so a manifest import carries it. `chat:write` covers every session and streaming
method. `files:read` lets the bot resolve and download inbound file shares.
`files:write` lets it return explicitly attached workspace files through
Slack's external upload flow. IronClaw never uses the retired `files.upload`
method. `reactions:write` lets the bot react to a message it delivers a notice
about on the message lane; live progress on the Agent surface shows in the
session itself. `channels:history` and its private/group/DM siblings also let
the bot read a streaming message back after a network fault mid-reply.
The `user` scopes are the union of what the sixteen personal Slack tools need.
`reactions:read`, `reactions:write`, and `im:write` joined that union when the
reaction and open-DM tools landed — an account connected before then holds a
narrower grant, and the first call to one of those tools answers with a
reconnect prompt. Reconnecting requests the wider set and updates the same
account in place; the earlier read-only tools keep working in the meantime.
Slack validates the request URL when you save the manifest, so start IronClaw before
creating the app.
---
## Configuration
Slack has no settings in the configuration file. The instance-wide values — bot token,
signing secret, workspace and app ids, and the OAuth client credentials — are entered
once under **Admin → Configuration** on the **Slack deployment configuration** card, and
the secrets go straight into the encrypted secret store. Each person then connects their
own Slack account from the **Extensions** page (or by asking the agent to "connect
Slack").
There is no channel allowlist or routing table. Inviting the bot into a channel is what
enables it: Slack only delivers a channel's events to the app because the bot is a
member, and each message runs as the person who mentioned the bot. In channels — shared
or not — the bot answers only when explicitly mentioned: a plain thread reply without a
mention is dropped without starting a run, though approve/deny replies and `/ironclaw`
commands still work; in DMs it answers every message.
There is no key that turns Slack on. The webhook route is always mounted, and it starts
accepting events once the Slack extension is installed and its signing secret is
registered. Until then it answers `503`. There is no `IRONCLAW_REBORN_SLACK_ENABLED`
environment variable either — the gate it fed was removed in #6116.
<Note>
An older configuration file may still carry a `[slack]` section. Nothing reads it. A
leftover setup field — `api_app_id`, `team_id`, `bot_token_env`, `signing_secret_env`,
`channel_routes`, and the rest — now stops startup with a pointer to the web interface
instead of being quietly ignored, so delete the section. `[slack].enabled` is accepted
but inert, so an older install keeps booting.
</Note>
---
## Troubleshooting
<AccordionGroup>
<Accordion title="Slack can't verify the request URL">
IronClaw has to be running and publicly reachable over HTTPS before you save the URL.
Confirm the instance answers from outside your network, and that you used the exact
`/webhooks/extensions/slack/events` path.
</Accordion>
<Accordion title="Slack rejects authorization with a redirect-URI mismatch">
The redirect URL registered in the Slack app must match the one IronClaw sends character
for character. Check the scheme, the host, the port, and that the path is the full
`/api/reborn/product-auth/oauth/slack/callback`.
</Accordion>
<Accordion title="The bot doesn't respond in a channel">
Invite the bot to that channel, and make sure you subscribed to the matching message
event. A bot only sees conversations it belongs to.
</Accordion>
<Accordion title="Requests are rejected">
A wrong or missing signing secret makes IronClaw reject every incoming request. Re-enter
it through the setup flow, then restart the server.
</Accordion>
<Accordion title="Replies fail with feature_disabled, not_agent_app, or missing_scope">
The app is installed without Slack's Agents feature, or its bot token predates it. Enable
**Agents** in the app settings (the manifest's `features.agent_view` block), confirm the
bot scopes include `assistant:write` and `chat:write`, reinstall the app, and update the
bot token under **Admin → Configuration** if Slack issued a new one. IronClaw does not
fall back to plain messages when the Agent surface is unavailable; the failed delivery
names the missing capability.
</Accordion>
<Accordion title="The Stop button does not appear">
Slack shows it only while a session is processing and only when the app subscribes to
`agent_session_stopped`. Add the event subscription (it is in the manifest above) and
reinstall the app.
</Accordion>
<Accordion title="I can't find where to configure Slack">
The instance-wide credentials live under **Admin → Configuration**, on the **Slack
deployment configuration** card. The **Extensions** page handles the personal half —
installing the extension and connecting your own Slack account.
</Accordion>
</AccordionGroup>