Skip to content

fix(feishu): fallback to original message_id for forwarded resource download - #63110

Open
luxuguang-leo wants to merge 1 commit into
NousResearch:mainfrom
luxuguang-leo:fix-feishu-forwarded-resource-fallback
Open

luxuguang-leo wants to merge 1 commit into
NousResearch:mainfrom
luxuguang-leo:fix-feishu-forwarded-resource-fallback

Conversation

@luxuguang-leo

@luxuguang-leo luxuguang-leo commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

When a user forwards a file or image in a Feishu group chat, the resource (file_key/image_key) belongs to the original message, not the forwarded copy. The download always fails because Feishu's message_resource.get API associates resources with the message they were first uploaded in.

This adds a fallback: when the initial download fails, resolve the source message_id via message.get API (checking root_idparent_idupper_message_id) and retry the download with it.

Both download paths are covered:

  • Image (_download_feishu_image) — retries with resolved source message_id
  • File/document (_download_feishu_message_resource) — same fallback

A shared helper _retry_with_forwarded_source prevents code duplication.

Changes Made

  • adapter.py (+77 lines):

    • _resolve_forwarded_source_message_id() — calls message.get via _run_blocking(), extracts root_idparent_idupper_message_id as source
    • _retry_with_forwarded_source() — shared helper called from both download paths on failure
    • Wired into _download_feishu_image — retries with resolved source when initial download fails
    • Wired into _download_feishu_message_resource — retries with resolved source when all request types fail
  • tests/gateway/test_feishu.py (+116 lines):

    • 6 unit tests for _resolve_forwarded_source_message_id (root_id, parent priority, no source, self-ref, API failure)
    • 4 integration tests for _retry_with_forwarded_source (image fallback, file fallback, no source, self-ref)

Review Comments Addressed

This PR fixes all three review points from the previous iteration:

# Review Status
1 Image path (_download_feishu_image) had no fallback — only file path did ✅ Both paths now have identical fallback via _retry_with_forwarded_source
2 Used asyncio.to_thread() instead of adapter's _run_blocking() ✅ Changed to await self._run_blocking(self._client.im.v1.message.get, request)
3 Missing E2E tests for full resolve → retry flow ✅ 10 new tests — 6 resolve + 4 retry (image + file + no-source + self-ref)

E2E Verification

Tested on a live Feishu group chat with require_mention: false:

  1. Forwarded image_download_feishu_image fails → fallback resolves source ID → retry succeeds → image cached locally ✅
  2. Forwarded PDF_download_feishu_message_resource fails → fallback resolves source ID → retry succeeds → PDF extracted and readable ✅
  3. Directly sent image (non-forwarded) → downloads via normal path, no fallback involved ✅ (no regression)

All 10 unit/integration tests pass:

$ python3 -m pytest tests/gateway/test_feishu.py -q -k "forwarded or resolve_forwarded or retry_with"
..........                                                               [100%]
10 passed, 214 deselected in 3.24s

How to Test

python3 -m pytest tests/gateway/test_feishu.py -q -k "forwarded or resolve_forwarded or retry_with"

Checklist

  • Bug fix (non-breaking change)
  • Tests added — 10 new, all passing + E2E verified on live Feishu
  • Commit follows Conventional Commits
  • Tested on macOS 15.7.3

@alt-glitch alt-glitch added type/bug Something isn't working comp/plugins Plugin system and bundled plugins platform/feishu Feishu / Lark adapter P3 Low — cosmetic, nice to have labels Jul 12, 2026

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for targeting a real missing fallback: current main returns empty values after resource-download failures in plugins/platforms/feishu/adapter.py:3893-3954.

Problems

  • The new retry is only in _download_feishu_message_resource() (PR plugins/platforms/feishu/adapter.py:3818). Images use _download_feishu_image() from the normalized.image_keys loop (plugins/platforms/feishu/adapter.py:3784-3788 on current main), so forwarded images still cannot use the fallback described in the PR.
  • The new message.get call uses asyncio.to_thread (PR plugins/platforms/feishu/adapter.py:3843). Use _run_blocking instead: commit b296915c82c9da02bd6edacf52490e68f85e1f16 moved Feishu SDK calls to the adapter-owned executor to prevent shared-executor shutdown failures.
  • TestForwardedResourceFallback only tests helper return values (PR tests/gateway/test_feishu.py:4953-5045), not a failed initial download followed by a successful alternate-ID retry, nor the image path.

Suggested changes

  • Share a bounded alternate-ID retry between file/media and image resource downloads, retain _run_blocking, and add behavior tests for both paths.

Automated hermes-sweeper review.

Comment thread plugins/platforms/feishu/adapter.py Outdated
# When the current message_id's resource download fails, the message may
# be a forward of an original message. Try fetching the message to see
# if root_id/parent_id points to a source message, and retry with that.
alt_id = await self._resolve_forwarded_source_message_id(message_id)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fallback is reachable only for normalized.media_refs; normalized.image_keys use _download_feishu_image() and still return on failure. Please apply the same bounded alternate-message retry to the image path or extract a shared helper, otherwise the PR's forwarded-image case remains broken.

Comment thread plugins/platforms/feishu/adapter.py Outdated
return None
try:
request = self._build_get_message_request(message_id)
response = await asyncio.to_thread(self._client.im.v1.message.get, request)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use await self._run_blocking(self._client.im.v1.message.get, request) here. Current main routes Feishu SDK calls through the adapter-owned executor after b296915 to avoid default-executor shutdown failures.

Comment thread tests/gateway/test_feishu.py Outdated
asyncio.run(_run())


class TestForwardedResourceFallback(unittest.TestCase):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an end-to-end downloader behavior test: make the initial resource request fail, resolve an alternate ID, and make the retry succeed. Cover both the message-resource and image download paths.

@teknium1 teknium1 added sweeper:risk-message-delivery Sweeper risk: may drop, duplicate, misroute, or suppress messages sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 15, 2026
@luxuguang-leo
luxuguang-leo force-pushed the fix-feishu-forwarded-resource-fallback branch from 8892553 to 9385801 Compare July 28, 2026 16:08
When a user forwards a file or image in a Feishu group chat, the
resource (file_key/image_key) belongs to the original message, not
the forwarded copy. Download fails because message_resource.get
associates resources with the message they were first uploaded in.

This adds a fallback that resolves the source message_id via
message.get API and retries the download with it.

- _resolve_forwarded_source_message_id: finds root_id/parent_id/
  upper_message_id from the forwarded message
- _retry_with_forwarded_source: shared helper called from both
  download paths on failure
- Both _download_feishu_image and _download_feishu_message_resource
  now retry with the resolved source message_id

Addresses all three review points from NousResearch#63110:
1. Image path now has the same fallback
2. Uses self._run_blocking() (standard pattern after b296915)
3. Full test coverage: 6 resolve + 4 retry integration tests
@luxuguang-leo
luxuguang-leo force-pushed the fix-feishu-forwarded-resource-fallback branch from c068f66 to 2ac3b50 Compare July 28, 2026 16:10
@luxuguang-leo

Copy link
Copy Markdown
Contributor Author

@teknium1 This PR has been updated addressing all three review points. E2E verified on live Feishu — forwarded images and PDFs now download correctly. Would appreciate a re-review when you get a chance.

This branch has not been deployed

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

Labels

comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have 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.

3 participants