🚀 Claude Code 子进程会话管理架构

基于飞书机器人的多用户、多会话隔离设计

📊 核心流程架构图

👤 用户
飞书群聊/私聊发送消息
📱 飞书客户端
WebSocket 长连接接收消息
识别群聊/私聊类型
🔐 会话管理器
生成会话标识
user-{用户ID} / group-{群ID}
💾 数据库查询
检查会话是否存在
获取 claude_session_id
获取 project_path
🤖 Claude 子进程
spawn('claude', ['-p', '--resume=xxx'])
工作目录:project_path
捕获 session_id
📤 流式响应
JSON 流式解析
累积文本缓冲
3秒或2000字符发送
✅ 消息返回
发送到飞书群聊
更新会话活跃时间
保存 claude_session_id

💡 关键设计亮点

100%
会话隔离
无状态
服务设计
自动
上下文恢复
3秒
流式响应

🔒 完全隔离

每个飞书会话拥有:

  • 独立的 Claude 子进程
  • 独立的工作目录
  • 独立的会话上下文
  • 独立的临时文件目录

💾 持久化存储

关键信息保存在 SQLite:

  • conversation_id(会话标识)
  • claude_session_id(子进程会话)
  • project_path(工作目录)
  • last_activity(最后活跃时间)

🔄 无状态恢复

服务重启后自动恢复:

  • 从数据库读取 session_id
  • 使用 --resume 恢复上下文
  • 清理 24 小时过期会话
  • 检测进程是否仍然存活

⚡ 流式优化

平衡实时性与性能:

  • 累积缓冲区:2000 字符
  • 最大延迟:3 秒
  • JSON 流式解析
  • 自动文件附件检测

🛡️ 安全防护

多重保护机制:

  • 禁用 cd 命令(防止目录错乱)
  • 工作目录只读原则
  • 防并发竞争(预注册机制)
  • 自动清理临时文件

🎯 智能路由

根据消息类型智能处理:

  • 文件命令:直接发送附件
  • Linux 命令:直接执行
  • /clear:清空会话上下文
  • 普通消息:调用 Claude 子进程

⏱️ 完整对话时序

🎬 场景:用户在飞书群聊中首次发送消息 展示从消息接收到响应返回的完整流程

第 1 步:消息接收

用户在飞书群聊中发送"帮我写个函数",飞书 WebSocket 客户端接收到事件,类型为 im.message.receive_v1

第 2 步:会话识别

会话管理器提取 chat_id 和 chat_type,生成会话标识:group-oc_8623156bb41f217a3822aca12362b068

第 3 步:数据库查询

查询 feishu_sessions 表,发现是新会话,返回:claude_session_id=null, project_path=/home/event

第 4 步:创建子进程

调用 spawn('claude', ['-p', '--output-format', 'stream-json', '帮我写个函数'], { cwd: '/home/event' })

第 5 步:捕获会话标识

子进程输出 {"type":"system","session_id":"session-abc123"},写入器捕获并保存到数据库

第 6 步:流式响应

子进程持续输出 JSON,解析 text 字段累积到缓冲区,每 3 秒或 2000 字符发送到飞书

第 7 步:完成并清理

子进程退出,发送剩余缓冲区内容,更新 last_activity,从进程池移除,清理临时文件

第 8 步:后续对话

用户再次发送"改一下这个函数",使用 --resume=session-abc123 恢复上下文,Claude 自动记住之前的对话

🔄 会话恢复机制详解

⚠️ 核心挑战:服务重启后如何恢复会话? 进程池(activeClaudeProcesses)存储在内存中,服务重启后会清空,但用户期望继续之前的对话

🎯 首次对话

创建新会话的完整流程:

  • 步骤 1:spawn Claude 子进程,不带 --resume 参数
  • 步骤 2:捕获系统输出的 session_id(如:session-abc123)
  • 步骤 3:保存到数据库:UPDATE feishu_sessions SET claude_session_id='session-abc123'
  • 步骤 4:注册到进程池:activeClaudeProcesses.set('session-abc123', process)

💬 后续对话

在同一会话中继续对话:

  • 步骤 1:从数据库读取 claude_session_id='session-abc123'
  • 步骤 2:spawn 时添加 --resume=session-abc123
  • 步骤 3:Claude 自动恢复上下文(对话历史 + 文件操作)
  • 步骤 4:进程退出后不清除数据库的 session_id

🔧 服务重启

重启后的智能恢复逻辑:

  • 启动时:批量清理 24 小时未活跃的 session_id
  • 首次请求:检测 session_id 是否在进程池中
  • 若不存在:清空 session_id,下次创建新会话
  • 若存在:继续使用(但重启后通常不存在)

🧹 自动清理

防止会话泄漏的策略:

  • 定时清理:启动时清理 24 小时未活跃会话
  • 进程退出:从 activeClaudeProcesses 移除
  • 临时文件:清理 ~/.claude-logs/ 下的 cwd 文件
  • /clear 命令:手动清空 session_id 重新开始

🛡️ 工作目录保护机制

📅 RCA 2024-12-04:cd 命令导致的状态不一致问题 原实现允许 cd 命令修改 session.project_path(内存),但未持久化到数据库,服务重启后文件被创建到错误目录

❌ 问题场景

用户执行以下操作序列:

  • cd /home/event/subdir(内存中 project_path 被修改)
  • 创建文件(文件被创建到 subdir)
  • 服务重启(内存状态丢失)
  • 继续对话(project_path 恢复为 /home/event)
  • 后续文件被创建到错误位置

✅ 解决方案

完全禁用 cd 命令:

  • 检测到 cd 命令时返回提示信息
  • 告知工作目录固定为 project_path
  • 建议使用相对路径访问子目录
  • 示例:ls subdir/ 或 cat subdir/file.txt

🔒 只读原则

工作目录管理规则:

  • project_path 在会话创建时由系统确定
  • 运行时任何代码都不允许修改
  • 数据库中无 updateProjectPath 函数(有意为之)
  • 需要修改必须手动操作数据库 + 重启服务

🗺️ 群聊目录映射

当前绑定关系:

  • 1-市场活动:/home/event
  • 2-案例库:/home/case
  • 3-WebX:/home/webx
  • 私聊用户:./feicc/user-{open_id}

⚙️ 并发控制与竞争防护

🎯 核心问题:如何防止同一会话的并发请求冲突? 用户快速连续发送多条消息,可能导致同时 spawn 多个 Claude 子进程,造成上下文混乱

竞争场景

用户在 1 秒内发送 3 条消息,每条消息触发 spawn 调用,但 session_id 的捕获是异步的,可能导致进程池中出现重复或丢失

预注册机制

在 spawn 之前,先用 processKey(临时标识)在 activeClaudeProcesses 中占位,值为 'pending',防止并发请求通过"会话繁忙"检查

进程替换

spawn 成功后,将 'pending' 替换为实际进程对象。捕获到 session_id 后,如果 processKey 不同,则更新映射关系

繁忙检测

isSessionBusy() 检查 activeClaudeProcesses 中是否存在该 session_id,存在则返回"正在处理中,请稍候"

// 预注册机制伪代码 const processKey = sessionId || `pending-${Date.now()}-${Math.random()}`; activeClaudeProcesses.set(processKey, 'pending'); // 先占位 const claudeProcess = spawn('claude', args); activeClaudeProcesses.set(processKey, claudeProcess); // 替换为实际进程 // 捕获 session_id 后更新映射 if (capturedSessionId !== processKey) { activeClaudeProcesses.delete(processKey); activeClaudeProcesses.set(capturedSessionId, claudeProcess); }

💾 数据库结构设计

📋 feishu_sessions 表

核心会话信息:

  • id:自增主键
  • conversation_id:唯一会话标识(user-xxx / group-xxx)
  • claude_session_id:Claude 子进程会话 ID
  • project_path:工作目录路径
  • session_type:会话类型(private / group)
  • last_activity:最后活跃时间

📝 feishu_message_log 表

消息历史记录:

  • session_id:关联会话 ID
  • direction:消息方向(incoming / outgoing)
  • content_type:内容类型(text / file / command)
  • content:消息内容
  • created_at:创建时间

🔑 查询示例

常用数据库操作:

  • 查询会话:SELECT * FROM feishu_sessions WHERE conversation_id='group-oc_xxx'
  • 更新活跃时间:UPDATE feishu_sessions SET last_activity=datetime('now') WHERE id=1
  • 清理过期会话:UPDATE feishu_sessions SET claude_session_id=NULL WHERE last_activity < datetime('now', '-24 hours')

🔍 调试命令

故障排查工具:

  • 查看活跃会话:sqlite3 server/database/auth.db "SELECT * FROM feishu_sessions WHERE claude_session_id IS NOT NULL;"
  • 检查子进程:ps aux | grep "claude.*resume"
  • 查看消息历史:SELECT * FROM feishu_message_log ORDER BY created_at DESC LIMIT 20;

🎓 架构总结

🏆 设计哲学:通过 SQLite + Claude --resume 实现分布式无状态服务 每个飞书会话完全隔离,服务重启不丢失上下文,支持水平扩展

✨ 优势

  • 完全隔离:不同用户/群聊互不干扰
  • 状态持久:服务重启自动恢复
  • 扩展性强:可部署多个实例共享数据库
  • 成本低:基于官方 Claude CLI,无需重复造轮子

⚠️ 注意事项

  • Claude CLI 需要授权(gaccode token)
  • 会话上下文有容量限制(200K tokens)
  • 长时间未使用的会话会过期
  • 子进程内存占用需要监控

🚀 性能优化

  • 流式响应:3秒缓冲降低消息频率
  • 预注册机制:防止并发竞争
  • 自动清理:定期清理过期会话
  • 临时文件隔离:避免磁盘占用

🔮 未来优化

  • 支持会话导出/导入
  • 实现会话共享机制
  • 增加使用量统计
  • 支持多租户隔离