Skip to content

fix(feishu): media/file sends in topic groups fail with 99992402 (invalid receive_id_type=thread_id) - #55067

Open
qioer0762 wants to merge 2 commits into
NousResearch:mainfrom
qioer0762:fix/feishu-thread-id-media-send
Open

fix(feishu): media/file sends in topic groups fail with 99992402 (invalid receive_id_type=thread_id)#55067
qioer0762 wants to merge 2 commits into
NousResearch:mainfrom
qioer0762:fix/feishu-thread-id-media-send

Conversation

@qioer0762

Copy link
Copy Markdown

Summary

Fixes #39526. In Feishu topic groups (话题群), media/file attachments sent by Hermes fail with [99992402] field validation failed and are silently dropped (the user receives nothing — not the thread, not the main chat). Plain text responses work because they take the ReplyMessage path; media sends reach the adapter with no reply anchor and fall into a CreateMessage call that uses receive_id_type="thread_id".

Root cause: thread_id is not a valid receive_id_type for Feishu's POST /im/v1/messages. Per the official docs the only accepted values are open_id / user_id / union_id / email / chat_id. Sending receive_id_type="thread_id" is rejected with 99992402.

I confirmed this independently against the live API (same app credentials, isolated topic chat): a bare POST /im/v1/messages?receive_id_type=thread_id with plain text returns 99992402, while the same payload with receive_id_type=chat_id succeeds — proving the failure is bound to the invalid receive_id_type, not the message/file type. This is the same defective branch reached by #35576 (auto-resume → stale thread_id99992402) via a different trigger.

Changes

Two changes that fix the whole bug class (all media siblings: images, files, audio, video), not just the .md case the reporter hit:

  1. gateway/platforms/base.py_thread_metadata_for_source() now carries a reply_to_message_id anchor for Feishu threads (falling back to the source message id). This lets media/file sends — which otherwise arrive at the adapter with no reply target — use the supported ReplyMessage + reply_in_thread=True path so the attachment lands inside the topic. Telegram/other-platform metadata is untouched (the anchor is added only on the feishu branch, and Telegram already uses its own telegram_reply_to_message_id key).

  2. plugins/platforms/feishu/adapter.py_send_raw_message() no longer emits the invalid receive_id_type="thread_id". When no reply anchor is available it degrades to chat_id delivery so the message still reaches the conversation instead of being dropped.

Tests

  • Replaced the existing change-detector test that froze the buggy receive_id_type=="thread_id" assertion with behavior invariants:
    • no reply anchor → degrade to chat_id (never thread_id)
    • reply anchor present → ReplyMessage with reply_in_thread=True
  • Added base.py regression tests: Feishu thread metadata carries reply_to_message_id (and falls back to the source message id) without leaking Telegram-only keys.

All updated logic verified with real imports against the changed modules (10/10 invariant checks pass). python -m ast parse + the patch tool's lint pass clean on all four files.

Why this approach

The supported way to place a message inside a Feishu topic is ReplyMessage with reply_in_thread=true anchored to a real om_ message id — not a thread_id receive type that the API rejects. When an anchor genuinely isn't available, chat_id delivery is an always-valid degrade (message reaches the conversation) and matches the existing fallback philosophy referenced in #35576.

Closes #39526.

…2402)

Feishu CreateMessage only accepts receive_id_type of
open_id/user_id/union_id/email/chat_id. The adapter's no-reply-anchor
branch sent receive_id_type="thread_id", which the API rejects with
[99992402] field validation failed — silently dropping media/file
attachments in topic groups (and stale-thread auto-resume sends).

Two changes that fix the whole class, not just .md files:

- gateway/platforms/base.py: _thread_metadata_for_source now carries a
  reply_to_message_id anchor for Feishu threads, so media/file sends
  (which otherwise reach the adapter with no reply target) can use the
  supported ReplyMessage + reply_in_thread path and land inside the
  topic. Telegram/other-platform metadata is untouched.
- plugins/platforms/feishu/adapter.py: when no reply anchor is
  available, _send_raw_message degrades to chat_id delivery instead of
  emitting the invalid thread_id receive_id_type, so the message still
  reaches the conversation rather than being dropped.

Relates to NousResearch#39526, NousResearch#35576.
- Replace the change-detector assertion that froze the buggy
  receive_id_type="thread_id" behavior with invariants: no-anchor sends
  degrade to chat_id, and anchored sends use ReplyMessage with
  reply_in_thread=True so media lands in the topic.
- Add base.py regression tests that Feishu thread metadata carries a
  reply_to_message_id anchor (and falls back to the source message id)
  without leaking telegram-only keys.

Relates to NousResearch#39526.
@alt-glitch alt-glitch added type/bug Something isn't working comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins platform/feishu Feishu / Lark adapter sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages P2 Medium — degraded but workaround exists labels Jun 29, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: part of the Feishu 99992402 (invalid receive_id_type) cluster. This fixes the media/file case via a different mechanism than the same-author pair #54497/#54498 — here a reply_to_message_id anchor is added in core gateway/platforms/base.py (with a chat_id degrade in the adapter), whereas #54497 fetches the last thread message inside the adapter. Closes #39526. Also related: #35576 (stale thread_id 99992402), #37425 (send_message Feishu topic delivery), and the broader open PR cluster #37322 / #37787. Flagging the cluster so a maintainer can pick the canonical fix.

@tchivs

tchivs commented Jul 14, 2026

Copy link
Copy Markdown

I consolidated this root-cause fix with #39563 in #64343, preserving the #39563 authored commit and adding strict topic routing so missing/invalid anchors cannot spill into the parent chat. The new PR also covers the adapter's invalid receive_id_type=thread_id branch and current-message anchoring.

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for tracing the invalid receive_id_type="thread_id" branch; current main still contains that exact call at plugins/platforms/feishu/adapter.py:4623-4651.

Problems

  • The proposed no-anchor chat_id fallback prevents 99992402 but can deliver a topic-scoped response into the parent chat. Current retry handling already avoids parent-chat fallback for a threaded withdrawn/missing reply target at plugins/platforms/feishu/adapter.py:4796-4825.
  • gateway/run.py:14357-14401 has a separate thread-metadata builder that this PR does not update. The streaming response-media flow invokes _deliver_media_from_response at gateway/run.py:12178-12180, so it can still lack the Feishu reply_to_message_id required by the adapter.
  • The standalone sender still passes only {"thread_id": thread_id} at plugins/platforms/feishu/adapter.py:5418 before sending media at :5431-5439; the proposed fallback would place those messages in the parent chat.

Suggested changes

  • Preserve this removal of the invalid API call, but pair it with gateway/run.py anchor propagation and fail closed for topic sends lacking a valid anchor rather than spilling to the parent chat. The linked consolidation PR #64343 contains that direction.

Automated hermes-sweeper review.

@tidytorch

Copy link
Copy Markdown

I reproduced this failure on Hermes v2026.7.30 / 0.19.1 in a real Lark topic group: the text reply stayed in the topic, but the requested native .txt attachment was absent. A current-source harness confirmed the same routing split described here: media with thread-only metadata selects CreateMessage, while an explicit message anchor selects ReplyMessage.

This branch is now merge-conflicted with main because _thread_metadata_for_source() gained Slack workspace metadata after the PR was opened. I rebased the two commits onto current main, preserved @qioer0762 as commit author, retained the newer Slack behavior, and ran the two affected test files (27 passed). The updated replacement is #76677.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/gateway Gateway runner, session dispatch, delivery comp/plugins Plugin system and bundled plugins P2 Medium — degraded but workaround exists platform/feishu Feishu / Lark adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feishu: MEDIA attachments sent via topic/thread reply land in main conversation instead of thread

5 participants