The Slack-native governance substrate for Claude Code — the kernel other governance tooling builds on. Humans, Claude Code sessions, and peer agents converse safely in shared channels: every tool call passes through a declarative, tier-aware policy engine; every decision lands in a hash-chained, Ed25519-signed audit journal you can verify offline. Per-thread session isolation, identity-aware permission gates, operator admin commands with cross-channel approval, peer-bot loop control, and defense-in-depth against prompt injection. The policy/journal/relay kernel here is the substrate that the companion agent-governance-plane (AGP) vendors for cross-runtime governance.
Links: Roadmap & Scope · Gist One-Pager · GitHub Pages · Release Notes
Research Preview — Channels require Claude Code v2.1.80+ and
claude.ailogin.
Slack workspace (cloud)
↕ WebSocket (Socket Mode — outbound only, no public URL)
server.ts (local MCP server, spawned by Claude Code)
↕ stdio (MCP transport)
Claude Code session
Socket Mode means no public URL needed — works behind firewalls, NAT, anywhere.
For AI-assisted setup: run /slack-channel:install and your AI assistant will walk you through every step below. The install skill also supports doctor (health check), verify (round-trip test), repair (auto-fix), manifest (one-click Slack-app import), reset, tour, and uninstall modes — see skills/install/SKILL.md.
Before step 1, confirm you have:
- Bun ≥ 1.0 — install with
curl -fsSL https://bun.sh/install | bash. Node.js / Docker fallbacks are documented in Option B / Option C below. - Claude Code ≥ v2.1.80 — see https://docs.claude.com/claude-code/install for upgrade.
claude.ailogin — this is a Research Preview constraint. API-key-only auth (ANTHROPIC_API_KEYset with noclaude.aisession) does NOT work for Channels. Runclaude loginto complete the browser flow.
- Go to api.slack.com/apps → Create New App → From scratch
- Socket Mode: Settings → Socket Mode → Enable → Generate App-Level Token (
xapp-...) withconnections:writescope - Event Subscriptions: Enable → Subscribe to bot events:
message.im— DMsmessage.channels— public channelsmessage.groups— private channelsapp_mention— @ mentions
- Bot Token Scopes (OAuth & Permissions):
chat:write— send messageschannels:history— read public channelsgroups:history— read private channelsim:history— read DMsreactions:write— add reactionsfiles:read— download shared filesfiles:write— upload filesusers:read— resolve display names
- Install to Workspace → Copy Bot Token (
xoxb-...)
/slack-channel:configure xoxb-your-bot-token xapp-your-app-tokenPick your runtime:
bun install
# Current (claude-code-plugins marketplace):
claude --channels plugin:slack-channel@claude-code-plugins
# Future (after upstream approval):
# claude --channels plugin:slack-channel@claude-plugins-officialnpm install
# In .mcp.json, change command to: "npx", args: ["tsx", "server.ts"]
claude --channels plugin:slack-channel@claude-code-pluginsdocker build -t claude-slack-channel .
# In .mcp.json, change command to: "docker", args: ["run", "--rm", "-i", "-v", "~/.claude/channels/slack:/state", "claude-slack-channel"]
claude --channels plugin:slack-channel@claude-code-pluginsThis step is the most common silent failure. Slack installs apps to the workspace without joining any channels. The pairing DM in step 4 works because DMs auto-route, but a channel test message in step 5 will hit silence — the bot literally isn't in the channel to see the event.
- Open Slack → navigate to the channel where you want the bot to live
- Click the channel name at the top → Integrations tab
- Click Add an App → select your bot
- Confirm: the bot now appears in the channel's member list
Private channels need explicit invitation. Repeat per channel if the bot needs to be in more than one.
- DM the bot in Slack — you'll get a 6-character pairing code
- In your terminal:
/slack-channel:access pair <code> - You're connected. Chat away.
In the channel where you added the bot in step 3.5, send:
@<bot-name> hello
The bot should reply within 10 seconds. If you get silence, run /slack-channel:install doctor for a structured diagnosis, then /slack-channel:install repair to auto-fix what's fixable. See Troubleshooting for the top failure modes.
The five silent-failure modes that cover ~95% of fresh-install issues:
- Bot is not in the channel — see step 3.5 above. Channel test messages hit silence because the bot can't see the event.
- Claude Code version too old — run
claude --version; need ≥ v2.1.80. claude.ailogin missing —ANTHROPIC_API_KEYalone is not accepted (Research Preview constraint). Runclaude login.- Bun not installed —
bun: command not found. Install withcurl -fsSL https://bun.sh/install | bashor use the Node.js fallback in Option B. - Wrong file permissions on
.env— must be0600. Runchmod 0600 ~/.claude/channels/slack/.env, or run/slack-channel:install repair.
Full troubleshooting matrix (10 failure modes): skills/install/references/troubleshooting.md.
Author rules in access.json.policy to automate permission decisions for Claude Code tool calls. Three rule effects, evaluated strictest-tier-first then first-applicable within a tier (detailed below):
{
"policy": [
{
"id": "safe-reads-in-ops",
"effect": "auto_approve",
"match": { "tool": "Read", "channel": "C_OPS_DOCS" }
},
{
"id": "no-shell",
"effect": "deny",
"match": { "tool": "Bash" },
"reason": "Shell execution is not permitted."
},
{
"id": "dangerous-writes",
"effect": "require_approval",
"match": { "tool": "Write", "channel": "C_DEPLOY" },
"approvers": 2,
"ttlMs": 300000
}
]
}auto_approve— skip the Block Kit prompt; the tool call runs immediately and apolicy.allowevent is journaled.deny— the reason is posted back into the originating thread and the call is rejected.policy.denyis journaled.require_approval— route through human approver(s).approvers: 2requires two distinct Slackuser_ids (NIST two-person integrity; the same user cannot double-satisfy quorum by clicking twice). A single deny from any allowlisted user rejects the request immediately regardless of quorum count.- Successful approvals grant a TTL window scoped to
(rule, channel, thread)so a chain of similar calls doesn't re-prompt. - Parse errors in
access.json.policyare fatal at boot — policy is safety-critical, silent degradation is not offered. Missing or emptypolicyis fine (first-install path).
Tier-aware evaluation: rules carry a tier (admin → user → workspace → default). Evaluation is strictest-tier-wins — a higher tier's deny cannot be overridden by a lower tier's auto_approve, so a workspace-level guardrail can't be relaxed by a per-user rule. Within a tier, first-applicable ordering holds.
When a call is denied, the result returned to Claude is context-stripped to a bare { behavior: 'deny' } — the rule id and reason are journaled and posted to the thread, but not handed back to the model, so a denied tool call can't be reverse-engineered into a rephrase-and-retry loop.
Full schema reference: ACCESS.md. Decision procedure: 000-docs/policy-evaluation-flow.md. Release scope and what was deliberately deferred: 000-docs/v0.6.0-release-plan.md.
See ACCESS.md for the full schema.
/slack-channel:access policy allowlist # Only pre-approved users
/slack-channel:access add U12345678 # Add a user
/slack-channel:access remove U12345678 # Remove a user
/slack-channel:access channel C12345678 # Opt in a channel
/slack-channel:access channel C12345678 --mention # Require @mention
/slack-channel:access status # Show current configChannels can opt in to cross-bot message delivery by listing trusted bot user IDs in allowBotIds. Useful when multiple Claude Code instances (or other bots you operate) need to coordinate in a shared channel — e.g., an ops-monitor agent and an engineering agent in #incidents. Default is no cross-bot delivery: every bot message is dropped at the gate. See ACCESS.md for the full schema and security tradeoffs.
Example access.json entry:
{
"channels": {
"C_INCIDENTS": {
"requireMention": false,
"allowFrom": ["U_OPS_BOT", "U_ENG_BOT", "U_HUMAN"],
"allowBotIds": ["U_OPS_BOT", "U_ENG_BOT"]
}
}
}Self-echoes from this bot are always filtered regardless of allowBotIds. Peer bots cannot approve permission prompts — the permission relay gates on the top-level allowFrom, not the channel policy.
For the full multi-agent recipe — registering a second bot, configuring allowBotIds mutually, mention-driven addressing, loop-prevention rate limit, !mute/!unmute operator verbs, common failure modes, what you DON'T get — see 000-docs/multi-agent-channels.md.
This is a prompt-injection vector by design. The security model is documented threat-model-first in SECURITY.md: (1) the adversary and the four principals, (2) the mitigations below — each with its own stated limit, and (3) an explicit list of what this does NOT protect against (same-UID host compromise, in-process token exposure, supply-chain, Slack/Anthropic platform bugs). Start there for the full picture; the defense-in-depth layers are:
- Sender gating: Every inbound message hits a gate. Ungated messages are silently dropped before reaching Claude.
- Outbound gate: Replies only work to channels that passed the inbound gate.
- File exfiltration guard: Cannot send
.env,access.json,audit.log, or other state files through the reply tool. - Prompt-injection defense: System instructions explicitly tell Claude to refuse pairing/access requests from Slack messages — peer-bot messages carry the same risk as human messages.
- Bot filtering:
bot_idmessages are dropped by default. Channels that host multiple cooperating agents can opt in to specific peers viaallowBotIds; self-echoes are always filtered viabot_id/bot_profile.app_id/usertriple-check. - Link unfurling disabled: All outbound messages set
unfurl_links: false, unfurl_media: false. - Token security:
.envischmod 0o600, never logged, never in tool results. - Static mode: Set
SLACK_ACCESS_MODE=staticto freeze access at boot (no runtime mutation). - Signed audit journal: Every tool-call decision is written to a hash-chained, Ed25519-signed journal (RFC 8785 JCS canonical form). The chain is verifiable offline against a published public key — see Audit Signing.
- Admin-command hardening: Operator verbs (
!clear,!restart) route through gate → policy → journal → execute and require a server-minted HMAC nonce confirmed from a second channel. Claude cannot self-invoke them — no MCP tool name begins withadmin.. This closes the EchoLeak / operator-coercion class (CVE-2025-32711, threat T11): a prompt injected into one channel cannot drive a privileged action, because confirmation must come from a channel the attacker doesn't control. - Peer-bot loop control: A per-
(channel, bot_id)sliding-window rate limit (default 10 msg/60s) breaks A→B→A runaway loops; operators can!mute <@bot>/!unmute <@bot>a misbehaving peer.
Each layer's limit, and an explicit "what this does NOT protect against" section, are in SECURITY.md. Trust boundaries, per-primitive attack surface, and threats T1–T11 are documented in 000-docs/THREAT-MODEL.md.
Every decision the gate makes is journaled to ~/.claude/channels/slack/audit.log — hash-chained and, as of v0.10, Ed25519-signed — so a third party can verify the log against a public key without trusting the host.
# Verify a journal end-to-end (hash chain + signatures):
bun server.ts --verify-audit-log ~/.claude/channels/slack/audit.log
# Operator key lifecycle (Ed25519 over RFC 8785 JCS):
bun audit-key-cli.ts init # generate keypair, encrypt private half to .env, print public key
bun audit-key-cli.ts rotate # fresh keypair, re-sign chain head, archive the old public key
bun audit-key-cli.ts verify # verify the log against active + archived public keys
bun audit-key-cli.ts show # print active public key + key idThe key is loaded at boot from SOPS+age-encrypted .env. Run with --no-audit-signing to fall back to hash-chain-only. Design + rotation lifecycle: 000-docs/audit-journal-architecture.md and 000-docs/key-management.md.
agent-governance-plane (AGP) is a companion governance tool that builds on this substrate. AGP vendors a pinned copy of CCSC's governance kernel — the policy engine (policy.ts), the hash-chained signed journal (journal.ts), the Slack relay, and nonce-bound HITL — to govern agent runtimes beyond a single Slack bridge. The relationship is one-way by design: CCSC is the substrate and has zero knowledge of AGP; AGP is the downstream consumer. If you need governance across multiple agent runtimes (not just Claude Code in Slack), AGP is the layer to look at; if you need a governed Slack channel for Claude Code, CCSC is all you need. The substrate-extraction contract is documented in AGP's 000-docs/009-AT-ADR-ccsc-substrate-extraction-strategy.md, and the patterns shared between the two are recorded in 000-docs/ADR-002.
Security-critical code earns a hard gate. ci.yml runs nine checks in sequence on every PR — a red on any one blocks merge:
| Gate | What it enforces |
|---|---|
bun run typecheck |
TypeScript strict, no any escape hatches |
| Biome lint | curated rule set |
bun test |
986 tests / 6,017 assertions across unit + property + Gherkin suites |
| coverage floor | ≥ 95% line + function (scripts/coverage-floor.sh) |
| dependency-cruiser | architecture invariants (e.g. policy.ts may never import manifest.ts) |
| Gherkin lint | acceptance-feature style, --strict |
| harness-hash verify | SHA-256 tamper check on pinned .feature + arch-rule files |
bun audit |
dependency CVEs at --audit-level=high |
| crap-score | cyclomatic-complexity ceiling (30) |
Out of band: CodeQL, gitleaks secret scanning, OpenSSF Scorecard, and manual Stryker mutation testing (bunx stryker run, baselines in 000-docs/MUTATION_REPORT.md). The five Wall-1 acceptance primitives live in features/*.feature and run against the real code via a hand-rolled Gherkin runner.
bun test # full suite
bun run typecheck # strict type check
bunx @biomejs/biome check .# Dev mode (bypasses plugin allowlist):
claude --dangerously-load-development-channels server:slackFull project one-pager and operator-grade system analysis
Every user-visible change is recorded in CHANGELOG.md (Keep a Changelog format). Release notes: GitHub Releases.
See CONTRIBUTING.md for branching, commits, PR workflow, and the quality-gate checklist. AI assistants helping with contributions should read AGENTS.md first.
- @jeremylongshore — author, maintainer
- @maui-99 — security hardening review (v0.3.0)
- @jinsung-kang — clean shutdown on client disconnect (v0.3.1)
- @CaseyMargell — event deduplication fix (v0.3.1), cross-bot delivery via
allowBotIds(v0.4.0)
This is open-source software maintained by Jeremy Longshore. If it saves you time, consider sponsoring on GitHub or buying a coffee — it funds continued development and keeps the project independent.
Apache License 2.0 — see LICENSE.