p.enthalabs

GitHub - acip/slack-claude-agent: Self-hosted, open source Claude in Slack alternative, for you or a small team.

**Self-hosted, open source Claude in Slack alternative, for you or a small team.**

@mention Claude in any Slack thread and get streaming replies, interactive question cards, and plan-approval gating before it can touch your files. One Claude Agent SDK session per thread, running on your own machine and workspace.

Anthropic's official Claude in Slack is available on Team and Enterprise plans. This is a self-hostable alternative that anyone can run: you bring your own machine, your own Anthropic credentials, and one project directory.

What it does

[](https://github.com/acip/slack-claude-agent#what-it-does)

- You @mention the bot in a channel. It opens a Claude session for that thread and streams the reply back into a single live-updating Slack message.

- Follow-up replies in the same thread continue the conversation with no re-mention needed. Each thread keeps its own Claude session, and sessions survive restarts.

- When Claude needs a decision, its questions render as native Slack radio or checkbox cards. Your selection resumes the thread.

- When Claude wants to change files, it must present a plan first. Write tools stay locked until you press **Approve** in Slack. You can also press **Request changes** to send feedback.

- Prefix a message with `plan:` to explicitly ask for a plan before any action.

Requirements

[](https://github.com/acip/slack-claude-agent#requirements) Please read the Security model before installing. This tool gives Claude real filesystem and shell access to a directory on the host, driven by Slack messages.

- **A dedicated VM (or at minimum a container).** This is a requirement, not a nice-to-have. The agent can run shell commands, so give it its own low-privilege sandbox rather than a box that holds production credentials, SSH keys, or other projects.

- **Node.js 18 or newer.**

- **Claude Agent SDK authentication.** Either a logged-in `claude` CLI on the host, or an `ANTHROPIC_API_KEY` in the process environment. The SDK spawns Claude as the user running this server, so that user must be authenticated.

- **A Slack workspace** where you can create and install an app.

- **A public HTTPS endpoint** that forwards to this server's port. Any reverse proxy or tunnel works: nginx, Traefik, Caddy, cloudflared, or ngrok. Cloudflare is optional; the examples below just use `cloudflared` because it is quick to start. See Set up a Cloudflare tunnel.

Quick start

[](https://github.com/acip/slack-claude-agent#quick-start) 1. **Clone and install.**``` git clone https://github.com/acip/slack-claude-agent.git cd slack-claude-agent npm install ```

2. **Expose the port over HTTPS.** Put a public HTTPS endpoint in front of the port you plan to use (default 3999) and copy its URL. Use whichever reverse proxy or tunnel you prefer (see Requirements). Quick throwaway tunnel with cloudflared: ``` cloudflared tunnel --url http://localhost:3999 ``` For a stable hostname, see Set up a Cloudflare tunnel.

3. **Create the Slack app.** Go to api.slack.com/apps, choose **Create New App**, then **From a manifest**, and paste `slack-app-manifest.yaml`. Replace `your-tunnel-host.example.com` in it with your tunnel hostname first. The manifest requests private-channel scopes (`groups:history`, `message.groups`) too; remove them if you only need public channels. 4. **Set both request URLs** to `https://<your-tunnel-host>/slack/events`. It goes in two places: **Event Subscriptions** and **Interactivity & Shortcuts**. People miss the Interactivity one, and then buttons do nothing. 5. **Install the app** to your workspace and copy the **Bot User OAuth Token** (`xoxb-...`) and the **Signing Secret**. 6. **Configure the environment.**``` cp .env.example .env ``` Fill in `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`, `PORT`, and `PROJECT_WORKSPACE`. 7. **Customize the persona (optional).**``` cp prompt.example.md prompt.md ``` Edit `prompt.md`. It is gitignored, so your version stays private. 8. **Start the server.**``` npm start ``` 9. **Invite and mention the bot.** In Slack, run `/invite @claude` in a channel, then mention it: `@claude what does this project do?`

Set up a Cloudflare tunnel

[](https://github.com/acip/slack-claude-agent#set-up-a-cloudflare-tunnel)

Cloudflare is optional (any reverse proxy or tunnel works), but `cloudflared` is a quick way to get a public HTTPS URL. Install it from Cloudflare's downloads (`brew install cloudflared`, `apt install cloudflared`, or a direct binary).

**Quick, throwaway tunnel** (random `*.trycloudflare.com` URL, no account needed, good for testing):

``` cloudflared tunnel --url http://localhost:3999 ```

It prints an HTTPS URL. That URL changes every run, so you would re-paste it into Slack each time.

**Named, persistent tunnel** (stable hostname on a domain in your Cloudflare account, best for production):

```

1. Authenticate (opens a browser; pick the domain to use)

cloudflared tunnel login

2. Create a tunnel and its credentials file

cloudflared tunnel create slack-claude-agent

3. Point a hostname at it (creates the DNS record for you)

cloudflared tunnel route dns slack-claude-agent slack-bot.yourdomain.com

4. Run it, forwarding to the local port

cloudflared tunnel run --url http://localhost:3999 slack-claude-agent ```

Your stable Slack request URL is then `https://slack-bot.yourdomain.com/slack/events`. To keep it running, install it as a service with `cloudflared service install` (see Cloudflare's docs), or manage it under pm2 alongside the app.

Configuration

[](https://github.com/acip/slack-claude-agent#configuration) All configuration is through environment variables (loaded from `.env`).

| Variable | Required | Default | Purpose | | --- | --- | --- | --- | | `SLACK_BOT_TOKEN` | yes | none | Bot User OAuth token (`xoxb-...`). | | `SLACK_SIGNING_SECRET` | yes | none | Verifies that requests really come from Slack. | | `PORT` | no | 3000 | HTTP port the server listens on. The `.env.example` uses 3999. | | `PROJECT_WORKSPACE` | yes | current dir | Absolute path to the one directory the agent may read and write. `CLAUDE_WORKSPACE` also works and takes precedence. | | `CLAUDE_MODEL` | no | `claude-sonnet-5` | Model id. `claude-opus-4-8` is the higher-quality upgrade. | | `AGENT_PROMPT_FILE` | no | `./prompt.md` | Path to a custom system prompt file. Falls back to a built-in default if missing. | | `CLAUDE_SETTING_SOURCES` | no | `project,local` | Which Claude settings layers the agent inherits. Add `user` to also inherit your global `~/.claude` config. See Security. | | `ALLOW_MCP_TOOLS` | no | off | Allow MCP tools (`mcp__*`) to run. Off by default (the gate blocks them). See Tips and tricks. |

Note: this app uses HTTP plus your signing secret. It does not use Socket Mode, so there is no `SLACK_APP_TOKEN` here by design.

Customizing the system prompt

[](https://github.com/acip/slack-claude-agent#customizing-the-system-prompt)

The bot appends a system prompt on top of the Claude Code preset. To change its persona or house rules, copy `prompt.example.md` to `prompt.md` and edit it. If neither `prompt.md` nor a file at `AGENT_PROMPT_FILE` exists, a built-in default is used and a warning is logged at boot. The prompt is read once at startup, so restart the server (or `pm2 restart slack-claude-agent`) after editing.

Tips and tricks

[](https://github.com/acip/slack-claude-agent#tips-and-tricks)

Add MCP servers (Notion, Google Drive, GitHub, and more)

[](https://github.com/acip/slack-claude-agent#add-mcp-servers-notion-google-drive-github-and-more)

The bot runs on the Claude Agent SDK, so it can use MCP servers to reach tools like Notion, Google Drive, GitHub, or your own. There are two parts: telling the SDK about the server, and letting this app's permission gate run the tool.

**1. Declare the server.** The SDK loads MCP servers from your Claude settings according to `CLAUDE_SETTING_SOURCES` (see Configuration). Two common options:

- Per project: create a `.mcp.json` file in your `PROJECT_WORKSPACE` directory. Because the default `CLAUDE_SETTING_SOURCES` includes `project`, it is picked up automatically. `${VAR}` expands environment variables at load time. {

"mcpServers": { "notion": { "type": "http", "url": "https://mcp.notion.com/mcp", "headers": { "Authorization": "Bearer ${NOTION_TOKEN}" } }, "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"] } } }

- Global: configure the server in your own `~/.claude` config, then set `CLAUDE_SETTING_SOURCES=user,project,local` so the bot inherits it. Note the Security caveat about the `user` layer before doing this.

**2. Allow the tools.** MCP tools are named `mcp__<server>__<tool>`, and this app's permission gate blocks unknown tools by default, so MCP tools stay denied until you opt in. Set `ALLOW_MCP_TOOLS=true` in `.env` and restart.

**Trust caveat.** When `ALLOW_MCP_TOOLS` is on, MCP tools run automatically and are **not** behind the plan-approval write gate. An MCP server can read and write in whatever it connects to. Only connect servers you trust, and scope their tokens as narrowly as the task needs (read-only where possible).

The details live in the Claude Agent SDK docs: MCP in the Agent SDK and the TypeScript SDK reference (`mcpServers`, `settingSources`, permissions).

Change the model

[](https://github.com/acip/slack-claude-agent#change-the-model) Set `CLAUDE_MODEL` in `.env`. `claude-opus-4-8` is the highest quality, `claude-sonnet-5` (the default) is the cost and speed balance.

Usage

[](https://github.com/acip/slack-claude-agent#usage) | Action | How | | --- | --- | | Start a thread | `@claude <your question>` in a channel the bot is in. | | Continue a thread | Just reply in the thread. No re-mention needed. | | Ask for a plan first | Prefix with `plan:`, for example `plan: refactor the auth module`. | | Approve changes | Press **Approve & proceed** on the plan card. This unlocks writes for that thread. | | Send feedback on a plan | Press **Request changes** and type what should change. | | Answer a question | Pick options on the card and press **Submit**. |

Running in production

[](https://github.com/acip/slack-claude-agent#running-in-production) Use a process manager and a persistent HTTPS endpoint.

``` pm2 start ecosystem.config.js pm2 logs slack-claude-agent pm2 restart slack-claude-agent ```

Run your reverse proxy or tunnel (nginx, Traefik, Caddy, cloudflared, ngrok, whichever you chose) as a managed service rather than an ad hoc terminal command, so the public URL stays stable. If the hostname changes, update both Slack request URLs.

Security model

[](https://github.com/acip/slack-claude-agent#security-model)

This tool runs the Claude Agent SDK on your host with real filesystem and shell access to the workspace directory you configure, driven by Slack messages. **Anyone who can post in a channel the bot is in can task an autonomous agent on your machine.** There is no per-user authorization. Channel membership is the access control.

How the permission gate actually works, straight from the code:

- Read tools (Read, Glob, Grep, WebSearch, WebFetch, TodoWrite) run automatically and unattended.

- A small allowlist of read-only Bash commands (things like `find`, `ls`, `cat`, `grep`, `git log`, `curl`) also runs automatically. Treat this as a convenience heuristic, not a security boundary. Commands like `cat` and `curl` can read or send arbitrary data, and prefix based allowlisting is a known soft spot. It lowers friction. It does not make Bash safe.

- Write tools (Edit, MultiEdit, Write, NotebookEdit, and full Bash) are locked per thread until you approve a plan. Approval unlocks writes for that one thread only.

- Approval is coarse. Approving a plan unlocks all write tools for the rest of that thread. It is not a per-command confirmation. Review plans before approving. This is also your main defense against prompt injection from files the read tools ingest automatically.

Other things worth knowing:

- **Slack authenticity.** Inbound events are verified by the Slack Bolt library using your `SLACK_SIGNING_SECRET`. Unsigned or unexpected requests get a 404. The 404 is noise reduction, not authentication, so the port must be reachable only through your intended HTTPS endpoint.

- **Never enable bypassPermissions.** Setting the SDK permission mode to `bypassPermissions` collapses the whole gate and lets writes run with no approval. The shipped code never enables it. Do not add it.

- **Settings inheritance.** The shipped default reads Claude settings, project `CLAUDE.md`, and MCP config from the `project` and `local` layers only. It deliberately skips your personal global `~/.claude` config. If you widen `settingSources` in `claude-slack.js` to include `user`, the agent will also inherit your global `CLAUDE.md` and any MCP credentials it references. Review before doing that.

Operator checklist:

- Run it inside a dedicated VM (or at least a container), as a low-privilege user, scoped to one project directory. Not as root, and not on a box holding production credentials or SSH keys the agent should never see.

- Restrict channel membership. Being in the channel equals the ability to run the agent.

- There is no built-in rate limiting. Exposure is bounded only by who is in the channel.

- API usage bills to your Anthropic credentials. A busy or hostile channel is a cost vector.

- If your `.env` is ever exposed, rotate the Slack tokens immediately.

Troubleshooting

[](https://github.com/acip/slack-claude-agent#troubleshooting) | Symptom | Likely cause | | --- | --- | | Bot stays silent | It is not in the channel, or the `message.channels` event and `channels:history` scope are missing. | | `url_verification` fails in Slack | Wrong `SLACK_SIGNING_SECRET`, or the tunnel is down. | | Requests 404 | The request URL must end in `/slack/events`. | | Buttons and cards do nothing | Interactivity request URL is not set. Set it to the same `/slack/events` URL. | | Thread stops continuing after a restart | The thread is not in `thread_session_map.json`. Mention the bot again to start fresh. |

How it works

[](https://github.com/acip/slack-claude-agent#how-it-works)

flowchart TD slack["Slack workspace<br/>@mention or thread reply"] proxy["HTTPS endpoint<br/>reverse proxy or tunnel"] server["server.js<br/>Bolt HTTP receiver<br/>signature check, de-dupe, plan: prefix"] bridge["claude-slack.js<br/>one session per thread, serialized turns"] sdk["Claude Agent SDK<br/>runs on your host and workspace"] gate{"Tool requested"} approve["Locked until you press Approve in Slack"]

slack -->|"events over HTTPS"| proxy proxy -->|"POST /slack/events"| server server --> bridge bridge --> sdk sdk --> gate gate -->|"read tools + safe Bash"| run["Run automatically"] gate -->|"write tools: Edit, Write, Bash"| approve approve -->|"approved for this thread"| run sdk -.->|"streamed reply, question and plan cards"| slack

subgraph host["Your machine (dedicated VM)"] server bridge sdk gate approve run end

Loading

`server.js` handles Slack wiring: mentions, thread replies, de-duplication of Slack's retries, and the `plan:` prefix. `claude-slack.js` is the bridge: it runs one Claude session per thread, serializes turns, gates tools, renders question and plan cards, and persists the thread-to-session map to `thread_session_map.json`.

License

[](https://github.com/acip/slack-claude-agent#license) MIT. See LICENSE.

Contributing

[](https://github.com/acip/slack-claude-agent#contributing) Issues and pull requests are welcome. This is a small project meant to stay small and legible, so please keep changes focused and explain the trust or security impact of anything that touches the permission gate.