← Atlas
Atlas expedition

Slack ↔ Claude Code remote control: architectures, recipes, and traps

Four working architectures for driving Claude Code from Slack, the projects that ship each, the SDK surface they lean on, and the security/cost traps every public deploy hits.

44 sources ~9 min read #24 claude-code · slack · agents · mcp · devops

Decision. If you can live with a hosted experience, use Claude Code in Slack [5] [23] — Anthropic ships it, sessions run on claude.ai/code, no infra to maintain. If you need self-hosted (private repos, custom MCP, on-prem secrets), pick chenhg5/cc-connect ⭐ 6.4k (Apr 2026) [12] for the Socket-Mode + multi-platform path, or wire anthropics/claude-code-action ⭐ 7.3k [28] behind a thin Slack→GitHub-Issue shim like AnandChowdhary’s bot [8] for the indirect / GitHub-Actions path. Build-it-yourself only if the use case is genuinely off-pattern — the Slack 3-second ack rule, prompt-injection from arbitrary chat input, and runaway-cost from open @-mentions [38] are all solved problems but easy to get wrong.

The four architectures, side-by-side

Pattern Slack transport Where Claude runs Strengths Weaknesses Reference repo
Managed (Claude Code in Slack) Anthropic-owned app claude.ai/code session [5] Zero ops; auto intent routing; Create-PR button Hosted only; consumer-account auth; no DMs [5] Anthropic Slack app [24]
Socket-Mode + local CLI Outbound WebSocket [1] Bot host runs claude -p No public ingress; per-thread cwd 10-conn cap per app [1]; workspace state to manage chenhg5/cc-connect ⭐ 6.4k [12], mpociot ⭐ 157 [6]
Indirect via GitHub Issue Events API webhook GitHub Actions runner Reuses claude-code-action; PRs natively; Cloudflare-cheap [8] 30-min poll cap [8]; GitHub-bound; no streaming tokens AnandChowdhary/claude-code-slack-bot [8]
Split worker + daemon Events API + git as queue Local daemon claude -p Public Slack endpoint, private compute; auto-commit per task [41] Two services to babysit; 5 s polling latency 41fred/claude-code-slack [41]

The split is real and load-bearing: Slack transport (Socket Mode vs Events API) and execution location (managed cloud, bot host, GHA runner, local daemon) compose independently. Any pair is achievable; pick the pair that matches the threat model and ops tolerance.

The Slack contract you cannot avoid

Three Slack-side rules force the architecture more than any Claude-side concern:

Rule Limit Why it matters Source
Ack within 3 s HTTP 200 ≤ 3000 ms Claude Code runs are minutes; you must ack-then-defer [2] [4]
Retry on miss Immediate, +1 min, +5 min; subscription disabled at >95% failure / 60 min Idempotency is mandatory; dedupe by event_id [2]
HMAC-SHA256 verify v0:ts:body, reject ts older than 5 min Replay-attack hardening; non-negotiable for Events API [3]
Message length 40,000 char hard cap, message_truncated warning at limit; 4,000 char recommended Diffs and tool logs blow past this; upload as snippet [31]
Socket Mode caps 10 concurrent WS per app; not Marketplace-eligible Blocks horizontally-scaled Marketplace listings [1]

The Bolt SDKs codify the workaround as lazy listenersawait ack() first, push to queue, do the work in a separate function [33]; ClaudeFluent’s Slack-bot recipe names the same constraint as the single most critical timing rule [34]. Even this leaks: Bolt-JS issue #1548 catalogs operation_timeout errors when ack() runs but the surrounding handler is slow under Lambda cold-start [32]. The canonical pattern is ack synchronously → SQS / Cloudflare Queue / Redis stream → worker calls claude -p [9]. For self-hosted Events API without a static IP, Cloudflare Tunnel is the 2026 default over ngrok because the hostname survives restarts and avoids re-registering the Slack URL [10].

Claude Code’s headless surface — what makes it bot-friendly

The CLI’s non-interactive surface is everything a Slack bot needs and was explicitly designed for it:

Flag What it does Bot use
-p / --print Non-interactive run, exits when done [19] Replace REPL with one-shot
--output-format stream-json NDJSON event stream on stdout [19] Parse line-by-line, stream to Slack
--include-partial-messages + --verbose Token-level deltas [19] Edit-in-place progress messages
--resume <session-id> / --continue / --fork-session Multi-turn over JSONL transcripts at ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl [21] Map thread_ts → session ID
--permission-mode <default\|acceptEdits\|plan\|auto\|dontAsk\|bypassPermissions> Override defaultMode from settings [20] bypassPermissions for full-auto, acceptEdits + allowlist for safer
--allowedTools "Bash(git diff *)" Glob-pattern tool allowlist [20] Scope tools per channel
--permission-prompt-tool <mcp_tool> Route approval requests to a named MCP tool [20] Slack approval buttons via MCP
--mcp-config <file\|json> + --strict-mcp-config Hermetic MCP load, ignores ~/.claude.json [20] Reproducible per-run MCP set
--max-turns, --max-budget-usd Hard runtime caps [20] Cost-runaway containment
--bare Skip hook/skill/MCP/CLAUDE.md auto-discovery; require ANTHROPIC_API_KEY [19] Reproducible bot runs
claude setup-token Mint long-lived OAuth token [20] CI / daemon auth

The Python claude-agent-sdk skips the spawn altogether: ClaudeSDKClient is an async context manager that auto-tracks the session, and CanUseTool: Callable[..., Awaitable[PermissionResult]] is the in-process equivalent of a permission prompt — it returns PermissionResultAllow (with optional updated_input) or PermissionResultDeny [22]. TypeScript’s query() exposes equivalent continue / resume / forkSession options on its options object [21].

⚠ Session-resume gotcha: transcripts are keyed on <encoded-cwd> — if your worker resumes from a different working directory than the original run, you silently get a fresh session [21]. Pin the cwd or ship the JSONL.

Existing projects, ranked

Project ⭐ Stars Lang Last push Architecture Worth it?
chenhg5/cc-connect [12] ⭐ 6.4k Go 2026-04-27 Socket Mode + multi-platform broker; admin dashboard; 10+ agents ✓ first pick for self-hosted
JessyTsui/Claude-Code-Remote [13] ⭐ 1.2k JS 2025-12-06 Hook + tmux/PTY injection; Slack still TODO ✗ no Slack yet
mpociot/claude-code-slack-bot [6] ⭐ 157 TS 2025-06 (stale) Socket Mode + SDK; per-thread cwd; image uploads ⚠ audit defaults — broad-permission Slack bots are the prompt-injection target [35] [40]
AnandChowdhary/claude-code-slack-bot [8] ⭐ 11 TS active Cloudflare Workers → GitHub Issue → claude-code-action; Cloudflare Queue mirrors comments every 10 s for ≤ 30 min ✓ for GHA-centric teams
yuya-takeyama/cc-slack [11] ⭐ 14 Go 2025-08-23 /cc slash command; multi-project ⚠ moderately maintained
dbenn8/claude-slack [14] ⭐ 22 Py 2026-01-31 Bidirectional via .claude/hooks/ + Socket Mode listener with shared response file ✓ for Python shops
jeremylongshore/claude-code-slack-channel [7] ⭐ 15 TS active Socket Mode + MCP stdio + per-thread isolation + hash-chained audit ✓ if security matters
41fred/claude-code-slack [41] ⭐ 15 Py active Split FastAPI worker (Railway) + local macOS daemon; GitHub repo as queue ✓ for laptop-as-runner setups
MattKilmer/claude-autofix-bot [42] ⭐ 3 TS active Express + simple-git on Railway; image attachment support; opens PR back ✓ for autofix flows
tomeraitz/claude-slack-bridge [44] ⭐ 21 TS active Inverse: MCP server lets a running Claude task pause and ask Slack via Unix socket ✓ for human-in-the-loop
takafu/slack-claude-bot [15] ⭐ 2 TS 2026-01-16 Per-thread CLI spawn; injects SLACK_BOT_TOKEN so Claude posts via Bash+curl ⚠ minimal but novel
engineers-hub-ltd/…integration [16] ⭐ 11 JS 2025-06-30 Wraps Claude Code as MCP server; /claude slash ⚠ stale
satetsu888/slackbot-for-claude [17] ⭐ 5 TS 2024-03 Pre-Claude-Code; raw Anthropic API ✗ abandoned

Anthropic’s first-party paths (Apr 2026)

Surface Status Trigger Notes
Claude Code in Slack [5] Research preview, launched 2025-12-08 [23] @claude mention in channel (no DMs) Auto intent routing; runs on claude.ai/code; admin chooses Code-only vs Code+Chat; “Retry as Code” button
Claude for Slack app [24] GA marketplace app Same Slack app surface as above The underlying Anthropic-published app — Claude.ai chat + Claude Code routing in one [25]
Claude Code Routines [26] Research preview, launched 2026-04-14 Schedule, API (/fire endpoint, bearer token), GitHub events Slack is not a native trigger — only available as MCP connector inside the routine
anthropics/claude-code-action [28] ⭐ 7.3k GA @claude in GitHub comments; issue_comment, pull_request_review_comment, issues [27]; also schedule, workflow_dispatch [28] Backends: Anthropic API, Bedrock, Vertex AI, Foundry [28]; Slack-trigger requires user-built bridge via repository_dispatch
Slack MCP server (Slack-published) [29] GA 2026-02-17 Read/write Slack from Claude.ai or Claude Code Auth gap: Claude Code requires OAuth 2.0 DCR; Slack’s MCP server doesn’t ship DCR → admins must hand-roll an internal app and pass --client-id/--client-secret [36]
@modelcontextprotocol/server-slack [30] ⭐ 84.6k (parent) Archived 2025-05-29 Superseded by Slack’s first-party MCP server

Bottom line: Anthropic ships the front door (Claude Code in Slack) and the backend primitive (claude-code-action), but no first-class Slack-trigger for self-hosted Claude Code. The community fills that gap.

Counter-argument: maybe don’t ship this

A Slack ↔ Claude Code bot inherits the trust, reach, and tone of the user account it runs as [35], and any workspace member who can @-mention can spend money [38]. If your use case is “trigger one repo’s tests from chat occasionally”, a /claude-fix slash command shelling claude-code-action via repository_dispatch is much smaller surface area than a long-running bot host. Build the bot only when streaming progress, multi-turn threading, or thread-pinned context actually pay for the operational debt.

Reference recipe: ack → queue → CLI → reply

A minimal self-hosted pipeline that satisfies every Slack constraint:

┌──────────────┐  Events API  ┌───────────────┐  enqueue  ┌──────────┐
│ Slack channel│─────HMAC────▶│  Slack worker │──────────▶│  Queue   │
└──────────────┘  (Cf Worker, └───────────────┘ (SQS, Cf  └──────────┘
       ▲          Lambda, etc.)│  ack ≤ 3 s    │ Queue, Redis)   │
       │                       │  add 👀 react │                 ▼
       │                       └───────────────┘          ┌──────────────┐
       │                                                   │   Worker     │
       │            chat.postMessage thread_ts             │ claude -p \  │
       └───────────────────────────────────────────────────│   --output-  │
                                                           │   format     │
                                                           │   stream-json│
                                                           └──────────────┘

Key choices each box:

  1. Slack worker — Cloudflare Workers (free tier, persistent hostname [10]) or AWS Lambda. Verify HMAC-SHA256 [3], reject ts > 5 min old, return 200, write payload to queue. Add an eyes reaction synchronously so users see the bot accepted the request even before the queue picks it up — AnandChowdhary’s bot does this [8].
  2. Queue — for ≤ 30 min runs, Cloudflare Queue with a 10 s poller works [8]; for longer, a stateful Redis stream or git-as-queue (41fred’s pattern, repo tasks/ directory polled every 5 s [41]).
  3. Worker — clones the repo into an ephemeral cwd, runs claude -p --output-format stream-json --verbose --resume <session-mapped-from-thread_ts> --max-budget-usd 5 --allowedTools 'Read,Edit,Bash(git *)' "$prompt". Pipe stdout through a NDJSON parser → chat.postMessage updates on the original thread_ts. Cap --max-turns defensively.
  4. Auto-commit & push — the 41fred daemon commits and pushes after each task automatically [41]; MattKilmer/claude-autofix-bot opens a PR with simple-git + the GitHub API and links it back to the Slack thread [42].
  5. Inject Slack context for free — takafu/slack-claude-bot’s clever shortcut: pass SLACK_BOT_TOKEN, CHANNEL, THREAD_TS as env into the Claude Code child process so Claude itself can use Bash+curl to post progress and reactions, no SDK plumbing needed [15].

For the SRE / production shape, David Calvert’s “Hive Ix” is <2,000 lines of Go on GCP/Kubernetes via Helm, calling exec.Command("claude", "-p", prompt) against Sonnet 4.5 on Vertex AI, with Atlassian + Datadog MCP servers attached for incident-response context [43].

Pitfalls — and what they cost

Pitfall What goes wrong Mitigation
3 s ack operation_timeout shown to user; partial delivery on bulk fan-outs [32] Lazy listeners; ack before any work [33]
40k char output Claude diffs / tool_use logs truncate silently Detect → upload as files.upload snippet [31]
Prompt injection Mitiga showed a working trojanized “skill” that uses Claude Code’s own Slack tools to phish coworkers from the user’s account, with public-channel fallback [35]; same class hits Gemini CLI, Copilot via comments [39] Least-privilege MCP tools [40]; allowlist channels; never trust message text
Cost runaway 24/7 operators report $2,000/hour from loop bugs [38]; any workspace member can @-mention --max-budget-usd and --max-turns per run; rate-limit per user_id
Insecure defaults in starter bots Many community bots ship with broad MCP permissions and no channel allowlist; a shared working directory across a channel is a common accident — broad-permission MCP is the documented prompt-injection vector [40] Audit any starter repo before deploy; least-privilege MCP, allowlist channels
Sandboxing skipped Without filesystem + network isolation, a compromised agent exfiltrates SSH keys [37] Run in a fresh container per request; restrict egress
OAuth DCR mismatch Claude Code’s MCP client requires OAuth 2.0 DCR; Slack’s MCP server doesn’t ship it [36] Hand-roll an internal Slack app; pass --client-id/--client-secret
Session-resume cwd drift Resume from a different cwd silently starts a new session [21] Pin cwd; or use a SessionStore adapter
Tool approval has no human Headless claude -p can’t pop a TUI prompt --permission-prompt-tool <mcp> for Slack-button approvals [20]; or --permission-mode acceptEdits with tight allowlist; OpenACP demos inline approve/deny buttons [18]

When to pick what

Hosted, GitHub-connected work → Claude Code in Slack [5]. Done.

Self-hosted, simple, 1 repo, behind firewall → chenhg5/cc-connect [12] (Socket Mode, no public IP).

GitHub-Actions–centric team, want PRs naturally → AnandChowdhary’s pattern [8]: Slack → GH Issue → claude-code-action → comments mirrored back. The 30-min poll cap is the hard limit.

Laptop is the runner, public Slack endpoint → 41fred split-worker pattern [41], git-as-queue.

Long-running runs that need human-in-the-loop → tomeraitz/claude-slack-bridge inverted MCP [44]: Claude itself blocks on a Slack reply mid-run.

Production SRE bot at scale → Calvert’s Helm/Vertex pattern [43] — minimal Go, MCP-attached observability.

Anything else → Routines API [26] for triggered runs; build a thin repository_dispatch bridge to claude-code-action for the chat-driven side.

Citations · 44 sources

Click the Citations tab to load…