Skip to content

Preserve Feishu topic reply target in OriginatingTo for normal messages #554

Description

@Dios-Man

Summary

Feishu topic/thread messages currently only preserve a concrete reply target in OriginatingTo for bare /new or /reset messages. For normal business messages inside a Feishu topic, delayed agent replies such as subagent completion/announce can lose the original message_id reply target and may create a new topic instead of replying inside the original topic.

Environment

  • OpenClaw: 2026.5.6
  • @larksuite/openclaw-lark: 2026.5.20
  • Feishu group chat mode: group_message_type=thread
  • threadSession=true behavior is active: inbound session keys include :thread:omt_*

Observed behavior

  1. A user sends a normal message in an existing Feishu topic and mentions an OpenClaw agent.
  2. Immediate parent-agent replies land in the original topic.
  3. A delayed reply triggered by subagent completion/announce may be sent to the bare chat target, e.g. chat:oc_*, which creates a new topic / lands outside the original topic.

The issue is not thread session detection itself. Logs showed inbound routing correctly used a session key like:

agent:<agent-id>:feishu:group:oc_REDACTED:thread:omt_REDACTED

The missing piece was carrying the concrete Feishu message_id reply target into the delayed outbound route.

Expected behavior

For every Feishu topic message, not only /new or /reset, OriginatingTo should preserve an encoded route target containing:

  • the chat target
  • __feishu_reply_to=<message_id>
  • __feishu_thread_id=<thread_id>

Then delayed replies can resolve the encoded route and call Feishu reply API with reply_in_thread=true.

Current code behavior

In src/messaging/inbound/dispatch.ts / compiled dispatch.js, originatingTo is currently guarded by isBareNewOrReset && dc.isThread:

const originatingTo = isBareNewOrReset && dc.isThread
  ? encodeFeishuRouteTarget({
      target: dc.feishuTo,
      replyToMessageId: params.replyToMessageId ?? params.ctx.messageId,
      threadId: dc.ctx.threadId,
    })
  : undefined;

This means normal topic messages do not populate OriginatingTo with the concrete reply target.

Verified local patch

We locally patched this to encode OriginatingTo for every topic message:

- const originatingTo = isBareNewOrReset && dc.isThread
+ const originatingTo = dc.isThread && dc.ctx.threadId
    ? encodeFeishuRouteTarget({
        target: dc.feishuTo,
        replyToMessageId: params.replyToMessageId ?? params.ctx.messageId,
        threadId: dc.ctx.threadId,
      })
    : undefined;

After applying this patch, delayed subagent announce/completion replies successfully returned to the original topic. Logs showed:

feishu: sendText: target=chat:oc_REDACTED#__feishu_reply_to=om_REDACTED&__feishu_thread_id=omt_REDACTED
feishu: resolved reply target from encoded originating route
feishu: replying to message om_REDACTED (msg_type=post, thread=true)

A/B validation

We also tested whether To needed to be changed to the encoded route. It does not appear necessary for this path.

A/B state:

To: dc.feishuTo,
OriginatingTo: opts.originatingTo ?? dc.feishuTo,

With only the dispatch.js / OriginatingTo patch above, subagent announce still replied to the original topic. This suggests the minimal fix is only to broaden OriginatingTo for all Feishu topic messages.

Suggested fix

Change the originatingTo guard from:

isBareNewOrReset && dc.isThread

to:

dc.isThread && dc.ctx.threadId

Optionally add a comment explaining that delayed inter-session/subagent replies need the concrete Feishu message_id because replying inside a Feishu topic requires replying to a message, not only sending to a chat/thread id.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions