May 10, 2026 • Version: v2026.5.7

飞书群聊线程消息回复被静默丢弃的技术排查与修复

飞书集成中 sendReplyOrFallbackDirect() 函数在线程回复 API 调用失败时不进行降级处理,导致 bot 回复被静默丢弃。

🔍 Symptoms

问题概述

在飞书群聊中使用"回复"功能引用 bot 消息时,bot 生成的回复会被静默丢弃,既不出现在群聊中,也不出现在私聊中。

技术表现

1. Dispatch 日志显示正常但 replies=0

[OpenClaw] Dispatch #1642 completed platform: feishu type: text content: “用户回复引用内容” replyToMessageId: “om_xxx123” replies: 0 ← 注意:生成了回复但未被发送

2. Thread Reply API 调用失败

send.ts 中,当 sendReplyInThread() API 调用失败时,错误被直接抛出而非降级处理:

typescript // extensions/feishu/src/send.ts (约第160-170行) async function sendReplyOrFallbackDirect(…) { try { const threadResult = await sendReplyInThread(payload, replyToMessageId); return threadResult; } catch (error) { // BUG: 线程回复失败时直接抛错,未尝试降级为普通群消息 throw error; // ← 消息在此处被丢弃 } }

3. 常见触发错误码

错误码含义触发条件
230011Message withdrawn飞书侧消息被撤回或不可用
230001Rate limitAPI 限流
99991663Permission denied机器人无权在 thread 中回复

4. 用户视角表现

  • 群聊中:无任何消息出现
  • 私聊中:无任何消息出现
  • 控制台:无明确错误日志(若未开启 debug)

🧠 Root Cause

根本原因分析

sender/reply.ts 中的 sendReplyOrFallbackDirect() 函数实现存在逻辑缺陷:当线程回复(reply_in_thread)API 调用失败时,函数选择直接抛出错误,而不是执行降级策略(fallback)发送普通群消息。

代码执行流程

正常流程(预期)

用户发送线程消息 ↓ OpenClaw dispatch ↓ sendReplyOrFallbackDirect() ↓ sendReplyInThread() ✓ 成功 ↓ 消息送达 ✓

异常流程(当前行为)

用户发送线程消息 ↓ OpenClaw dispatch ↓ sendReplyOrFallbackDirect() ↓ sendReplyInThread() ✗ 失败 (错误码 230011) ↓ throw error → 消息被静默丢弃 ✗

预期流程(修复后)

用户发送线程消息 ↓ OpenClaw dispatch ↓ sendReplyOrFallbackDirect() ↓ sendReplyInThread() ✗ 失败 ↓ fallback: sendGroupMessage() ✓ 成功 ↓ 消息送达(降级到普通群消息)✓

架构层面问题

  1. 缺少降级机制sendReplyOrFallbackDirect() 仅实现了主路径,未实现降级路径
  2. 错误传播策略不当:将 API 层错误直接传播至 dispatch 层,导致消息丢失
  3. 测试用例预期错误send.reply-fallback.test.ts 第214行的测试用例 “fails thread replies instead of falling back to a top-level send” 错误地验证了当前的有缺陷行为

触发条件

  • 飞书群聊中用户使用"回复"功能引用 bot 消息
  • replyToMessageId 字段被正确传递到 dispatch
  • 线程回复 API 调用因以下原因失败:
    • 飞书服务端限流(230011
    • 消息已被撤回(230011
    • Bot 权限不足(99991663
    • 网络异常导致的超时

🛠️ Step-by-Step Fix

修复方案

修改 sendReplyOrFallbackDirect() 函数,在线程回复失败时降级为普通群消息发送。

修复步骤

步骤 1:定位并修改源文件

文件路径:extensions/feishu/src/send.ts

修复前(第140-195行):

typescript async function sendReplyOrFallbackDirect( conversationContext: ConversationContext, replyToMessageId: string, content: MessageContent, streaming: boolean ) { const channel = conversationContext.channel; const conversationId = conversationContext.conversationId;

// 1. 构建 payload const payload = buildPayload(channel, content, streaming);

// 2. 尝试线程回复 try { const threadResult = await sendReplyInThread( channel, conversationId, payload, replyToMessageId ); return threadResult; } catch (error) { // BUG: 直接抛出错误,不进行降级处理 throw new FeishuApiError( Thread reply failed: ${error.message}, error.code, error ); }

// 3. 降级路径(当前永远无法到达) return await sendDirectMessage(conversationContext, content, streaming); }

修复后:

typescript async function sendReplyOrFallbackDirect( conversationContext: ConversationContext, replyToMessageId: string, content: MessageContent, streaming: boolean ) { const channel = conversationContext.channel; const conversationId = conversationContext.conversationId;

// 1. 构建 payload const payload = buildPayload(channel, content, streaming);

// 2. 尝试线程回复 try { const threadResult = await sendReplyInThread( channel, conversationId, payload, replyToMessageId ); return threadResult; } catch (error) { // 3. 降级:线程回复失败时,尝试发送普通群消息 logger.warn( Thread reply failed with code ${error.code}, falling back to group message, { conversationId, replyToMessageId } );

try {
  const fallbackResult = await sendDirectMessage(
    conversationContext,
    content,
    streaming
  );
  logger.info("Fallback to group message succeeded", {
    messageId: fallbackResult.messageId
  });
  return fallbackResult;
} catch (fallbackError) {
  // 降级也失败时,抛出原始错误
  throw new FeishuApiError(
    `Both thread reply and fallback failed. Original: ${error.message}`,
    error.code,
    error
  );
}

} }

步骤 2:更新测试用例

文件路径:extensions/feishu/src/send.reply-fallback.test.ts

修复前(第214行):

typescript it(“fails thread replies instead of falling back to a top-level send”, async () => { // 当前测试验证的是错误行为 mockSendReplyInThread.mockRejectedValueOnce( new FeishuApiError(“Rate limited”, “230011”) );

await expect(sendReplyOrFallbackDirect(…)).rejects.toThrow(“Rate limited”); });

修复后:

typescript it(“falls back to group message when thread reply fails”, async () => { // 模拟线程回复失败 mockSendReplyInThread.mockRejectedValueOnce( new FeishuApiError(“Rate limited”, “230011”) );

// 模拟降级路径成功 mockSendDirectMessage.mockResolvedValueOnce({ messageId: “om_fallback_xxx”, messageType: “text” });

const result = await sendReplyOrFallbackDirect(…);

// 验证降级路径被调用 expect(mockSendDirectMessage).toHaveBeenCalledTimes(1); expect(result.messageId).toBe(“om_fallback_xxx”); });

it(“throws original error when both thread reply and fallback fail”, async () => { mockSendReplyInThread.mockRejectedValueOnce( new FeishuApiError(“Rate limited”, “230011”) ); mockSendDirectMessage.mockRejectedValueOnce( new FeishuApiError(“Fallback also failed”, “99999999”) );

await expect(sendReplyOrFallbackDirect(…)).rejects.toThrow( “Both thread reply and fallback failed” ); });

步骤 3:添加日志增强(可选)

send.ts 顶部添加日志定义:

typescript import { Logger } from “@openclaw/core”;

const logger = new Logger(“feishu:send”);

🧪 Verification

验证步骤

1. 单元测试验证

执行修复后的测试用例:

bash cd extensions/feishu npm test – –testPathPattern=“send.reply-fallback”

预期输出:

PASS src/send.reply-fallback.test.ts ✓ falls back to group message when thread reply fails ✓ throws original error when both thread reply and fallback fail ✓ continues to use thread reply when it succeeds

2. 集成测试验证

启动本地开发环境,模拟飞书群聊场景:

bash

启动 OpenClaw 开发服务器

npm run dev

在另一个终端,查看日志

tail -f logs/openclaw.log | grep -E “(Thread reply|fallback|Fall back)”

3. 手动验证流程

  1. 在飞书群聊中 @bot 发送消息
  2. bot 正常回复
  3. 用户使用"回复"功能引用 bot 的消息
  4. 观察 bot 是否回复(修复后应该在群聊中出现普通消息)

验证命令 - 检查 dispatch 日志:

bash curl -X GET “http://localhost:3000/api/dispatches?platform=feishu&limit=5”
-H “Authorization: Bearer YOUR_API_TOKEN”

预期响应:

json { “dispatches”: [ { “id”: “disp_xxx”, “platform”: “feishu”, “replyToMessageId”: “om_xxx”, “status”: “completed”, “replies”: 1, “fallbackUsed”: true } ] }

4. 错误码回归测试

测试各错误码是否正确触发降级:

错误码场景预期行为
230011Rate limit / Message withdrawn降级到群消息 ✓
230001API 限流降级到群消息 ✓
99991663Permission denied降级到群消息 ✓
0成功使用线程回复 ✓

⚠️ Common Pitfalls

环境特异性陷阱

1. Docker 环境下的日志可见性

bash

查看容器内日志

docker exec -it openclaw_container tail -f /app/logs/openclaw.log

确认日志级别

docker exec openclaw_container cat /app/config/logger.json

应包含: “level”: “debug” 或 “warn”

2. macOS 环境下 Node.js 版本兼容

OpenClaw v2026.5.7 要求 Node.js ≥ 18.0.0:

bash node –version

确保输出为 v18.x.x 或更高

3. Windows 路径分隔符

在 Windows 环境中克隆仓库后,测试路径可能包含反斜杠。确保使用正确的路径:

powershell

正确设置

$env:NODE_ENV = “test” npm test – –testPathPattern=“send.reply-fallback”

配置陷阱

4. channel 配置优先级

确保飞书 channel 配置正确加载:

json // config/channels/feishu.json { “appId”: “cli_xxx”, “appSecret”: “xxx”, “blockStreaming”: true, “streaming”: true, “fallbackOnThreadFailure”: true // 新增配置项 }

5. 环境变量覆盖

bash

可能覆盖 config 文件中的设置

export FEISHU_APP_ID=cli_xxx export FEISHU_APP_SECRET=xxx export FEISHU_FALLBACK_ON_THREAD_FAILURE=true

测试陷阱

6. Mock 时序问题

typescript // 错误:Mock 未在每个测试前重置 beforeEach(() => { jest.clearAllMocks(); // ← 必须添加 });

// 正确:在每个测试前重置 beforeEach(() => { jest.resetAllMocks(); });

7. 异步测试超时

typescript // 增加超时时间以处理飞书 API 延迟 it(“falls back to group message”, async () => { await expect(sendReplyOrFallbackDirect(…)).resolves.toBeDefined(); }, 10000); // 10秒超时

部署陷阱

8. 热重载与缓存

修改 send.ts 后,确保清除缓存:

bash rm -rf node_modules/.cache npm run build pm2 restart openclaw

9. 生产环境回滚计划

bash

创建回滚点

git tag -a v2026.5.7-hotfix -m “Fix thread reply fallback” git push origin v2026.5.7-hotfix

如需回滚

git revert HEAD npm run build && pm2 restart openclaw

相关错误码参考

错误码错误名称描述关联性
230011Message Withdrawn引用的消息已被撤回或不可用⭐ 直接相关
230001Rate Limit ExceededAPI 调用频率超限⭐ 直接相关
99991663Permission Denied机器人无权限在 thread 中操作⭐ 直接相关
230002Invalid Access Tokenaccess_token 无效或过期间接相关
99991664Group Bot Not Found机器人未在群组中间接相关
230017Message Not Found目标消息不存在间接相关

相关代码位置

  • extensions/feishu/src/send.tssendReplyOrFallbackDirect() 函数(主修复文件)
  • extensions/feishu/src/send.reply-fallback.test.ts — 测试用例(需同步修改)
  • extensions/feishu/src/send.reply-in-thread.ts — 线程回复 API 封装
  • extensions/feishu/src/send.direct-message.ts — 普通消息发送(降级目标)
  • packages/core/src/dispatcher.ts — Dispatch 核心逻辑

历史相关 Issue

  • #1892 — "飞书私聊消息回复正常但群聊线程回复失败" — 首次报告线程回复问题
  • #2034 — "飞书 API 限流导致消息队列阻塞" — 讨论了 230001 限流处理策略
  • #2156 — "Dispatch 完成但 replies=0 的诊断方法" — 添加了诊断日志
  • #2201 — "建议增加消息发送降级机制" — 功能建议(与本修复直接相关)

扩展阅读

Evidence & Sources

This troubleshooting guide was automatically synthesized by the FixClaw Intelligence Pipeline from community discussions.