A sample Slack agent that showcases Slack Code built with Bolt for Python and the Claude Agent SDK using models from Anthropic. Mention it in a channel with a task and a project repo, and it decides whether to answer in place or create a dedicated code channel for the work. In a code channel it clones the repo into a temporary workspace and makes changes with the necessary approvals, keeping the channel's plan, context bar, and code tab current as it goes.
Warning
This agent runs commands generated by a language model on the machine running the app. Only run it against trusted repositories on a machine you're comfortable letting it operate. Refer to Command approvals for how command approvals work.
The coding agent works like this:
- You
@Coding Agent fix the flaky login test in <repo>in any channel, or send the agent a direct message. - The agent decides how to handle it, reading the thread or direct message the request came from. A question gets an answer in place. Work that takes more than one message gets a code channel, created with the
create_code_channeltool and linked back to your message. - Slack seeds the new channel with your request, mention included, and that mention starts the session there, along with the conversation the request came from. The agent clones the repo, pins it to the context bar, writes a plan, and registers a few slash commands for the channel.
- It reads and edits files and runs other commands in the clone. Riskier commands ask for your approval first.
- It publishes the diff to the code tab and summarizes what it did.
- Keep chatting in the channel (no
@-mention needed), comment on the plan, or use its slash commands to iterate. Messages sent while the agent works go into its next turn together. - When you say it's done, the agent posts a summary, which is shared back to your original message, and archives the channel.
Each code channel method is a tool the model calls when it decides to, in agent/tools/, one file per tool. The tools are served to the model by an in-process MCP server, so it sees each one as mcp__coding-agent__<name>:
| Tool | Method |
|---|---|
create_code_channel |
agents.conversations.create |
set_context_bar |
agents.conversations.setProperties |
publish_diff |
agents.conversations.setView (diff) |
publish_html_view |
agents.conversations.setView (html) |
write_plan |
canvases.create + agents.conversations.setView (canvas), then agents.conversations.setCanvasContent |
read_plan |
agents.conversations.getCanvas |
list_views |
agents.conversations.listViews |
remove_view |
agents.conversations.removeView |
set_commands |
agents.conversations.setCommands |
rename_session |
agents.sessions.rename |
archive_channel |
chat.postMessage + agents.sessions.setStatus (closed) + agents.conversations.archive |
clone_repo |
Local git clone into a temp directory |
read_file, write_file, edit_file |
Local files in that directory |
run_command |
A local program in that directory, run without a shell |
- Plan as a canvas. The plan is attached with comment access, so people can comment but only the agent edits.
read_plangives the agent open comments each turn, andsetCanvasContentkeeps comments anchored to the sections it didn't change. - Agent slash commands. Commands registered with
set_commandsarrive as slash commands. One catch-all listener (listeners/commands/) turns/run-tests unitinto a turn, so the agent decides what each command does. - Stop button. On
agent_session_stopped, the app flags the session so the in-flight run calls no more tools and posts nothing more, replaces any pending Approve / Deny buttons so they can't resume it, confirms the stop in the channel, and sets the session toactive. Slack doesn't change the status on a stop, so the app has to. - Archiving from Slack. On
channel_archiveorgroup_archive, the app forgets the session and deletes its working directory, the same cleanuparchive_channeldoes when the agent wraps up. - Picking up code channels. A message, mention, or slash command in a code channel the app has no state for, whether the seeded request in a channel the agent just created, one started from Slack's UI, or one forgotten after a restart, is recognized with
conversations.info(properties.record_channel.record_type == "agent_channel") and picked up as a session. Other channels are looked up once and remembered. - The conversation comes along. A new session reads the thread or direct message its channel was started from (
properties.agent_session.origin_link) and gives it to the agent's first turn, so a request like "fix this" keeps its context. - One turn at a time. Messages that arrive while the agent works wait, and go into its next turn together, each labeled with who sent it. The stop button drops them along with the turn.
- Creating a channel is the agent's call. The prompt in
agent/prompts.pyfollows Slack's guidance: create a channel for multi-step work like changing code or running tests, and reply in place for anything that fits in one message. - Idempotent creation.
create_code_channeltakes only a title. Itssession_idcomes from the triggering message, so a repeated call or a retried event reuses the same channel. The first turn only ever starts from the seeded mention, so it runs once.
Every tool call the coding agent makes goes through a Claude Agent SDK PreToolUse hook (agent/guardrails.py), and for run_command it asks command_needs_approval in agent/approvals.py:
- Read-only commands and local git changes run with no prompt. A path outside the clone always asks. Commands run without a shell, so they can't chain a second command, and what the approval check reads is exactly what runs.
- File edits don't go through approvals.
write_fileandedit_fileonly write inside the clone. - Anything else shows Approve and Deny buttons in the code channel, and the command runs only if someone in the channel approves it.
The agent gets only these tools. Claude Code's built-in tools, like its own Bash and Edit, are turned off, and so are the settings, CLAUDE.md files, and MCP servers configured on the machine running the app.
Before getting started, make sure you have a development workspace where you have permissions to install apps.
Join the Slack Developer Program for exclusive access to sandbox environments for building and testing your apps, tooling, and resources created to help you build and grow.
Install the Slack MCP and Skills Plugin so your coding agent can create, run, and deploy Slack apps, look up Web API methods and scopes, and build Block Kit without guessing.
Run this in a Claude Code session:
/plugin install slack@claude-plugins-official
Run this in your terminal:
codex plugin marketplace add slackapi/slack-skills-plugin
codex plugin add slack@slackRun this in a Cursor Agent chat:
/add-plugin slack
Install the skills with npx skills, using your agent's identifier:
npx skills add slackapi/slack-skills-plugin -y -a <agent>
# For example, OpenCode
npx skills add slackapi/slack-skills-plugin -y -a opencodeUsing Slack CLI
Install the latest version of the Slack CLI for your operating system:
You'll also need to log in if this is your first time using the Slack CLI.
slack loginslack create my-coding-agent --template slack-samples/bolt-python-coding-agent --subdir claude-agent-sdk
cd my-coding-agentUsing App Settings
- Open https://api.slack.com/apps/new and choose "From an app manifest"
- Choose the workspace you want to install the application to
- Copy the contents of manifest.json into the text box that says
*Paste your manifest code here*(within the JSON tab) and click Next - Review the configuration and click Create
- Click Install to Workspace and Allow on the screen that follows. You'll then be redirected to the App Configuration dashboard.
Before you can run the app, you'll need to store some environment variables.
- Rename
.env.sampleto.env. - Open your apps setting page from this list, click OAuth & Permissions in the left hand menu, then copy the Bot User OAuth Token into your
.envfile underSLACK_BOT_TOKEN.
SLACK_BOT_TOKEN=YOUR_SLACK_BOT_TOKEN- Click Basic Information from the left hand menu and follow the steps in the App-Level Tokens section to create an app-level token with the
connections:writescope. Copy that token into your.envasSLACK_APP_TOKEN.
SLACK_APP_TOKEN=YOUR_SLACK_APP_TOKENgit clone https://github.com/slack-samples/bolt-python-coding-agent.git my-coding-agent
cd my-coding-agent/claude-agent-sdkpython3 -m venv .venv
source .venv/bin/activate # for Windows OS, .\.venv\Scripts\Activate instead should workpip install -e ".[dev]"This app uses Claude through the Claude Agent SDK: claude-sonnet-5-5 for the coding agent and claude-haiku-4-5 for deciding whether a request needs a code channel.
- Create an API key from your Anthropic dashboard.
- Rename
.env.sampleto.env, if you haven't already. - Save the Anthropic API key to
.env:
ANTHROPIC_API_KEY=YOUR_ANTHROPIC_API_KEYThe Claude Agent SDK runs the Claude Code CLI that comes bundled with it, and the CLI keeps each session's conversation as a transcript under ~/.claude/projects/, including tool inputs and outputs like file contents and command output. Follow-up turns resume from that transcript.
Using OAuth HTTP Server (with ngrok)
This mode uses an HTTP server instead of Socket Mode, which is required for OAuth-based distribution.
- Install ngrok and start a tunnel:
ngrok http 3000- Copy the
https://*.ngrok-free.appURL from the ngrok output.
Using Slack CLI
-
Update
manifest.jsonfor HTTP mode:- Set
socket_mode_enabledtofalse - Replace
ngrok-free.appwith your ngrok domain (e.g.YOUR_NGROK_SUBDOMAIN.ngrok-free.app)
- Set
-
Create a new local dev app:
slack install -E local- Update your
.envOAuth environment variables:- Run
slack app settingsto open App Settings - Copy Client ID, Client Secret, and Signing Secret
- Run
SLACK_CLIENT_ID=YOUR_CLIENT_ID
SLACK_CLIENT_SECRET=YOUR_CLIENT_SECRET
SLACK_SIGNING_SECRET=YOUR_SIGNING_SECRET- Start the app:
slack run app_oauth.py- Open
https://YOUR_NGROK_SUBDOMAIN.ngrok-free.app/slack/installto install the app to your workspace via OAuth.
Using the Terminal
-
Create your Slack app at api.slack.com/apps/new using
manifest.json. Before pasting the manifest, setsocket_mode_enabledtofalseand replacengrok-free.appwith your ngrok domain. -
Copy the following values from Basic Information into your
.env:- Signing Secret
- Client ID and Client Secret
SLACK_CLIENT_ID=YOUR_CLIENT_ID
SLACK_CLIENT_SECRET=YOUR_CLIENT_SECRET
SLACK_SIGNING_SECRET=YOUR_SIGNING_SECRET- Start the app:
python3 app_oauth.py- Open
https://YOUR_NGROK_SUBDOMAIN.ngrok-free.app/slack/installto install the app to your workspace via OAuth.
Note: Each time ngrok restarts, it generates a new URL. You'll need to update the ngrok domain in
manifest.jsonand re-install the app.
Once the agent is running, there are several ways to interact:
App Home — Open the agent in Slack and click the Home tab to see how to start a task.
Direct Messages — Open the agent's Messages tab and ask a question or describe a task, or pick a suggested prompt. It answers there, or links a code channel for bigger work.
Channel @mentions — Invite the agent to a channel by typing /invite @agent-name in the message box, then @mention it in a thread with a question about the conversation, like what did we decide here?, and it answers in the thread. @mention it with a task and a repo, like add a "hello world" to the README of <repo>, and it replies with a link to a new code channel for the work.
Code Channels — Follow the work in the new channel. Approve or deny commands as they come up, comment on the Plan tab, review the diff in the code tab, and send follow-ups without an @mention. Say you're done to wrap up and archive the channel.
# Run ruff check from root directory for linting
ruff check
# Run ruff format from root directory for code formatting
ruff format# Run pytest from root directory for unit testing
pytest .manifest.json is a configuration for Slack apps. With a manifest, you can create an app with a pre-defined configuration, or adjust the configuration of an existing app.
app.py is the entry point for the application and is the file you'll run to start the server. This project aims to keep this file as thin as possible, primarily using it as a way to route inbound requests. It runs Bolt's AsyncApp in Socket Mode, so a long agent turn never holds up the stop button, an approval click, or another channel, and agent slash commands are routed to it without a slash_command_url.
app_oauth.py is an alternative entry point that runs the app in HTTP mode instead of Socket Mode. This is intended for deployments that use OAuth for app distribution. See the OAuth HTTP Server section under Development for setup instructions.
Every incoming request is routed to a "listener". This directory groups each listener based on the Slack Platform feature used.
/listeners/events — Handles incoming events:
agent_session_stopped.py— Stops the session when the stop button is pressed.app_home_opened.py— Publishes the App Home view with how to start a task.app_mentioned.py— Answers a mention in place or hands it to a code channel, and runs turns for mentions inside one.channel_archive.py— Forgets a code channel archived from Slack.message.py— Answers direct messages like mentions, and runs a turn for messages in a code channel.
/listeners/actions — Handles interactive components:
approval_buttons.py— Resumes a paused run when Approve or Deny is clicked.
/listeners/commands — Handles slash commands:
agent_command.py— Turns any command the agent registered into a turn.
agent.py— Defines the routing agent and the coding agent with their tools, and starts and resumes their runs. Along withguardrails.pyandtools, it's the only code that uses the Claude Agent SDK.prompts.py— Holds both agents' prompts.turns.py— Runs each turn for the listeners: it answers requests, sets the session status, runs the agent, and posts the outcome or the Approve / Deny buttons. It also queues messages that arrive mid-turn and handles approvals and the stop button.outcome.py— Defines theRunOutcomethatagent.pyreturns andturns.pyreads.context.py— Reads the thread or direct message a request came from, so the agent can follow the conversation.guardrails.py— Holds the SDK hooks around tool calls: the approval check forrun_command, which defers a command that needs approval, and the check that skips every tool call once the session is stopped.sessions.py— Keeps each code channel's session in memory: its working directory, plan canvas, conversation history, and messages waiting for the next turn. It also defines theAgentDepsandRequestDepsdataclasses passed to the agents at runtime, providing access to the Slack client and the session or request.approvals.py— Decides which commands need approval and holds runs paused on one.runtime.py— Holds what the tools and hooks share during a run: its deps, which the SDK can't pass to a tool, and the shape of a tool's reply.workspace.py— Runs commands in the clone without the app's secrets, and keeps file tools inside it.tools— Contains one file per tool the agents can call.