将本文件粘贴给 Claude Code,它会自动创建所有文件并完成配置。
在 Claude Code 的 Agent Team 工作时,自动在 Slack 创建 Thread,实时推送会话主题、成员加入、任务完成等信息。会话结束后发送汇总统计并标记完成。
项目/
├── .claude/
│ ├── hooks/
│ │ └── slack_notifier.py ← 核心通知脚本
│ └── settings.local.json ← hooks 配置 + 环境变量(不进 git)
Claude Code hooks 通过 stdin 传入 JSON。以下是各事件实际传入的关键字段(踩坑总结,务必按此实现):
| 事件 | 关键字段 | 说明 |
|---|---|---|
| SessionStart | cwd, session_id, source |
source 可能是 startup/resume/clear/compact |
| UserPromptSubmit | prompt, session_id |
prompt 是用户输入的完整文本 |
| SubagentStart | agent_type, agent_id |
用 agent_type 取可读名称(如 “cpo”),内置类型如 “Explore”/”general-purpose” 需特殊处理 |
| SubagentStop | agent_type, agent_id, last_assistant_message |
agent_type 有时为空,需 fallback 到 id 映射 |
| TaskCompleted | task_subject, task_description, teammate_name |
优先取 task_subject |
| SessionEnd | cwd, reason |
reason: clear/logout/prompt_input_exit 等 |
所有事件都包含 cwd、session_id、hook_event_name。
SLACK_BOT_TOKEN(xoxb- 开头)SLACK_CHANNEL_ID(C 开头)~/.claude/agent-team-slack-state/{cwd_hash}.jsoncwd(非 session_id),确保 Lead 和所有 Teammate 共享同一 Threadthread_ts、top_msg_ts、top_msg_text、session_id、members、agent_id_map、topic、task_count、start_time、roundfcntl.flock 做文件锁,锁文件使用 .lock 后缀(与 state 文件分离),Slack API 调用放在锁外_locked_read_modify_write(cwd, modifier) 模式:acquire lock → read → modify → write → release lock,modifier 函数内不应调用 Slack APISessionStart 将 session_id 写入 statehook.session_id == state.session_idagent_type 作为显示名general-purpose、Explore、Plan、statusline-setup、claude-code-guide):统一显示为 “C.C.”SubagentStart 时存储 agent_id → 显示名 映射到 stateSubagentStop 时 agent_type 可能为空,需查映射取可读名称,最终 fallback 为 “C.C.”error=ratelimited,读 Retry-After headerSessionStart
session_id,初始化 members、agent_id_map、task_count 等thread_ts、top_msg_ts、top_msg_text、topicUserPromptSubmit
topic 为空)prompt 截取前 40 字符作为会话主题(换行符替换为空格)_ensure_thread 创建 thread 时自动带上chat.update 更新顶层消息:🤖 TeamRoom:{项目名} | {主题} | {时间}state["top_msg_text"](SessionEnd 会读它追加 ✅)SubagentStart
agent_id → 显示名 映射👤 {名称} 加入讨论(当前成员:xxx、xxx)SubagentStop
agent_type(非空且非 "unknown"),否则查 agent_id_map,最终 fallback “C.C.”thread_ts 时直接跳过(Thread 未创建的纯内置 agent 场景)✅ {名称} 完成 ┃ {时间}last_assistant_message 摘要(截断 500 字)TaskCompleted
✅ 任务完成:{task_subject}(截断 300 字),递增 task_countSessionEnd
chat.update 更新顶层消息追加 ✅PostToolUse(含 Thread 懒创建逻辑)
tool_name == "SendMessage" 的事件,其他工具调用直接跳过_ensure_thread() 创建_ensure_thread() 创建顶层消息时自动带上已存的 topic:🤖 TeamRoom:{项目名} | {主题} | {时间}_ensure_thread() 内部有并发保护:多个 agent 同时触发时,只有第一个会实际创建 Threadmessage 为 dict 类型时(shutdown_request/shutdown_response/plan_approval 等结构化消息)不发到 Slackround💬 {sender} → {recipient} ┃ R{round} {时间}\n{消息摘要}{
"env": {
"SLACK_BOT_TOKEN": "xoxb-你的Token",
"SLACK_CHANNEL_ID": "C你的ChannelID"
},
"hooks": {
"SessionStart": [{"hooks": [{"type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/slack_notifier.py\"", "timeout": 10, "async": true}]}],
"UserPromptSubmit": [{"hooks": [{"type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/slack_notifier.py\"", "timeout": 10, "async": true}]}],
"SubagentStart": [{"hooks": [{"type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/slack_notifier.py\"", "timeout": 10, "async": true}]}],
"SubagentStop": [{"hooks": [{"type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/slack_notifier.py\"", "timeout": 10, "async": true}]}],
"TaskCompleted": [{"hooks": [{"type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/slack_notifier.py\"", "timeout": 10, "async": true}]}],
"PostToolUse": [{"matcher": "SendMessage", "hooks": [{"type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/slack_notifier.py\"", "timeout": 10, "async": true}]}],
"SessionEnd": [{"hooks": [{"type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR/.claude/hooks/slack_notifier.py\"", "timeout": 10, "async": true}]}]
}
}
用 settings.local.json(非 settings.json),避免 Token 进 git。
🤖 TeamRoom:voice-agent-project | 讨论 Voice Agent 下一阶段技术优先级 | 23:41:32 ✅
├─ 👤 ceo 加入讨论(当前成员:ceo)
├─ 👤 cto 加入讨论(当前成员:ceo、cto)
├─ 💬 ceo → cto ┃ R1 23:42:15
│ 我们先对齐技术优先级 ...
├─ 💬 cto → ceo ┃ R2 23:43:22
│ 同意,LiveKit 迁移放第一优先 ...
├─ ✅ ceo 完成 ┃ 23:45:11
│ CEO-CTO 双人会议总结 ...
├─ ✅ cto 完成 ┃ 23:45:28
│ 技术侧最终确认 ...
├─ ✅ C.C. 完成 ┃ 23:46:03
│ C.C. 报到,Slack 通知系统运行正常。
├─ ✅ 任务完成:更新 CONTEXT.md
└─ 🏁 团队会话结束
👥 参与成员:ceo、cto、C.C.
💬 对话轮次:5 轮
✅ 完成任务:1 个
⏱️ 持续时间:12 分钟
SLACK_BOT_TOKEN="xoxb-xxx" SLACK_CHANNEL_ID="Cxxx" \
echo '{"hook_event_name":"SessionStart","cwd":"'"$(pwd)"'","session_id":"test-123"}' | \
python3 .claude/hooks/slack_notifier.py 2>&1
Slack 收到消息即成功。如果有报错看 stderr 输出。
chat:write、chat:write.public/invite @你的App名| 错误写法 | 正确写法 | 影响 |
|---|---|---|
hook.get("agent_name") |
hook.get("agent_type") |
SubagentStart/Stop 拿不到可读名称 |
hook.get("tool_output") |
hook.get("tool_response") |
PostToolUse 取不到工具返回值 |
hook.get("task_description") |
hook.get("task_subject") |
TaskCompleted 取不到任务标题 |
SubagentStart 能拿到 agent_type(如 “ceo”),但 SubagentStop 有时只有 agent_id(十六进制串如 aad8fc744691eacb2)。必须在 SubagentStart 时存映射,SubagentStop 时查映射。
Claude Code 的 Explore、general-purpose、Plan 等内置 agent 也会触发 SubagentStart/Stop。如果不过滤,成员列表会出现这些技术名称。用 BUILTIN_AGENT_TYPES 集合统一映射为 “C.C.”。
State 文件按 cwd hash 存储。如果前一个 session 的 state 没被新 session 的 SessionStart 覆盖(比如 SessionStart 失败),后续事件会发到旧 Thread。用 session_id 校验解决。
python.org 安装的 Python(非 Homebrew)默认没有 SSL 证书,调 Slack API 报 SSL: CERTIFICATE_VERIFY_FAILED。
修复:运行一次 /Applications/Python {版本}/Install Certificates.command
当前版本 Claude Code 中 SubagentStart/SubagentStop 事件不稳定触发。不应依赖 SubagentStart 来创建 Thread。改为在首次 PostToolUse(SendMessage)时通过 _ensure_thread() 自动创建。SubagentStart 仅用于存储 agent_id 映射和发送成员加入通知(如果 thread 已存在)。
Agent 之间的 SendMessage 中,message 字段有时为 dict 类型(如 shutdown_request、shutdown_response、plan_approval),这些是结构化协议消息,不应发到 Slack。在 handle_post_tool_use 中用 isinstance(message, dict) 过滤。
PostToolUse 是 必须启用 的 hook,因为 Thread 懒创建和对话轮次计数都依赖它。当前实现仅处理 SendMessage 工具,其他工具调用在 handler 入口直接 return。
如果未来需要扩展监听其他工具,12 个 agent 并行时单次讨论可以产生 100+ 条文件操作消息,必须做白名单过滤。建议只记录高信号工具(TeamCreate、Agent、WebSearch、Bash、mcp__* 等),跳过 Read、Write、Edit、Glob、Grep 等。