Fix: Feishu topic replies create new threads instead of replying within existing topic
Problem Description
When a bot replies to messages in Feishu topic groups, it creates new sub-threads instead of replying within the original topic thread. This happens specifically when users send new messages directly in a topic (not replying to other messages).
Root Cause
Feishu API Behavior
Feishu message events return different fields based on user action:
Scenario A: User sends new message in topic
{
"message_id": "om_abc123",
"thread_id": "omt_xyz789", // Topic ID
"root_id": null, // ❌ NULL - not a reply
"parent_id": null // ❌ NULL
}
Scenario B: User replies to a message in topic
{
"message_id": "om_new456",
"thread_id": "omt_xyz789",
"root_id": "om_root001", // ✅ Has value
"parent_id": "om_parent002" // ✅ Has value
}
According to Feishu documentation:
"root_id: Root message ID, only returned in reply message scenarios"
Current OpenClaw Logic
const replyTargetMessageId = ctx.rootId ?? ctx.messageId;
// When rootId is null, falls back to current messageId
await client.im.message.reply({
path: { message_id: replyTargetMessageId }, // Current message!
data: { reply_in_thread: true }
});
// Result: Creates new thread rooted at current message ❌
Solution
When in a topic but root_id is not provided by Feishu, actively fetch the topic's root message ID via API:
// Add helper function
async function fetchThreadRootMessageId(
client: FeishuClient,
threadId: string,
log: (msg: string) => void
): Promise<string | null> {
try {
log(`[thread-fix] Fetching root message for thread ${threadId}`);
const res = await client.im.message.list({
params: {
container_id_type: "thread",
container_id: threadId,
page_size: 1,
sort_type: "ByCreateTimeAsc" // Earliest message (root)
}
});
if (res.code === 0 && res.data?.items?.length > 0) {
const rootMsgId = res.data.items[0].message_id;
log(`[thread-fix] Found root message: ${rootMsgId}`);
return rootMsgId;
} else {
log(`[thread-fix] Failed: code=${res.code}, items=${res.data?.items?.length || 0}`);
}
} catch (err) {
log(`[thread-fix] Error: ${err.message}`);
}
return null;
}
// In message processing logic (before determining replyTargetMessageId):
if ((isTopicSession || configReplyInThread) && ctx.threadId && !ctx.rootId && !ctx.parentId) {
const client = createFeishuClient(account);
ctx.rootId = await fetchThreadRootMessageId(client, ctx.threadId, log);
}
const replyTargetMessageId = isTopicSession || configReplyInThread
? ctx.rootId ?? ctx.parentId ?? ctx.messageId
: ctx.messageId;
Files to Modify
Based on the compiled output, the source file should be:
extensions/feishu/src/monitor.ts or similar
- Look for the function handling message reply logic
- Two locations need modification (error handling + normal reply)
Search for:
const replyTargetMessageId = ... ? ctx.rootId ?? ctx.messageId : ctx.messageId;
Locations in compiled code (for reference):
dist/monitor-*.js line ~1780 (error handling)
dist/monitor-*.js line ~2005 (normal reply)
Testing
Before Fix
- User sends message in topic → Bot creates new thread ❌
- First reply ("Acknowledged") → In topic ✅ (timing-dependent)
- Second reply (final result) → New thread ❌
After Fix
- User sends message in topic
- Bot fetches root message via API
- Both replies use root message ID → All in same topic ✅
Test Results
Tested on OpenClaw 2026.4.9:
- Topic ID:
omt_1abbdddc1bce9ce6
- Root message ID fetched:
om_x100b52d11f9e50b8b4ab28b8d7f034f
- Result: ✅ Both bot replies stayed in the original topic
Debug log:
[thread-fix] Fetching root message for thread omt_1abbdddc1bce9ce6
[thread-fix] Found root message: om_x100b52d11f9e50b8b4ab28b8d7f034f
replyTarget resolved - rootId=om_x100b52d11f9e50b8b4ab28b8d7f034f, final=om_x100b52d11f9e50b8b4ab28b8d7f034f
Related Issues
Configuration Required
Users must enable:
{
"channels": {
"feishu": {
"replyInThread": "enabled",
"groupSessionScope": "group_topic"
}
}
}
Performance Impact
- Adds one API call per topic message (when root_id is not provided by Feishu)
- Typical latency: ~400ms (observed in testing)
- Could be optimized with caching (thread_id → root message_id mapping)
Potential Optimization
Add LRU cache for thread_id → root message_id mapping:
const threadRootCache = new LRUCache<string, string>({ max: 100, ttl: 3600000 });
if (cached = threadRootCache.get(ctx.threadId)) {
ctx.rootId = cached;
} else {
ctx.rootId = await fetchThreadRootMessageId(...);
threadRootCache.set(ctx.threadId, ctx.rootId);
}
Tested by: Justin Wang
Date: 2026-04-14
Version: OpenClaw 2026.4.9
Fix: Feishu topic replies create new threads instead of replying within existing topic
Problem Description
When a bot replies to messages in Feishu topic groups, it creates new sub-threads instead of replying within the original topic thread. This happens specifically when users send new messages directly in a topic (not replying to other messages).
Root Cause
Feishu API Behavior
Feishu message events return different fields based on user action:
Scenario A: User sends new message in topic
{ "message_id": "om_abc123", "thread_id": "omt_xyz789", // Topic ID "root_id": null, // ❌ NULL - not a reply "parent_id": null // ❌ NULL }Scenario B: User replies to a message in topic
{ "message_id": "om_new456", "thread_id": "omt_xyz789", "root_id": "om_root001", // ✅ Has value "parent_id": "om_parent002" // ✅ Has value }According to Feishu documentation:
Current OpenClaw Logic
Solution
When in a topic but
root_idis not provided by Feishu, actively fetch the topic's root message ID via API:Files to Modify
Based on the compiled output, the source file should be:
extensions/feishu/src/monitor.tsor similarSearch for:
Locations in compiled code (for reference):
dist/monitor-*.jsline ~1780 (error handling)dist/monitor-*.jsline ~2005 (normal reply)Testing
Before Fix
After Fix
Test Results
Tested on OpenClaw 2026.4.9:
omt_1abbdddc1bce9ce6om_x100b52d11f9e50b8b4ab28b8d7f034fDebug log:
Related Issues
Configuration Required
Users must enable:
{ "channels": { "feishu": { "replyInThread": "enabled", "groupSessionScope": "group_topic" } } }Performance Impact
Potential Optimization
Add LRU cache for thread_id → root message_id mapping:
Tested by: Justin Wang
Date: 2026-04-14
Version: OpenClaw 2026.4.9