fix(feishu): render markdown tables via post instead of force-text downgrade - #36877
fix(feishu): render markdown tables via post instead of force-text downgrade#36877banditburai wants to merge 2 commits into
Conversation
|
Duplicate of the long-standing Feishu markdown-table rendering cluster: #21778 (canonical bug), with multiple competing open fix PRs (#26108, #29552, #33641, #31056, #27739). Same root cause — |
|
any updates? |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for isolating the table-routing behavior and covering both send and edit paths.
Problems
- Current main moved the active adapter from
gateway/platforms/feishu.pytoplugins/platforms/feishu/adapter.pyin560010547. The current table downgrade remains atplugins/platforms/feishu/adapter.py:4524-4534, so this PR's legacy-path diff does not modify the runtime implementation. - The added tests import
gateway.platforms.feishu(PRtests/gateway/test_feishu.py:32), while current tests importplugins.platforms.feishu.adapter(tests/gateway/test_feishu.py:489). - The new unconditional
postroute applies to both domains, but the adapter explicitly supports Lark via_domain_name(plugins/platforms/feishu/adapter.py:4718-4751) and the PR description leaves Lark rendering unconfirmed.
Suggested changes
- Port the implementation and test imports/patch targets to
plugins/platforms/feishu/adapter.py. - Keep the Lark path on text until it is validated, or add verified domain-specific coverage.
Automated hermes-sweeper review.
| if _MARKDOWN_HINT_RE.search(content): | ||
| # _MARKDOWN_TABLE_RE and the post row-splitter match on literal "\n", so a | ||
| # CRLF table would slip through detection and leak raw pipes as plain text. | ||
| content = content.replace("\r\n", "\n").replace("\r", "\n") |
There was a problem hiding this comment.
Current main migrated the runtime adapter to plugins/platforms/feishu/adapter.py in 560010547; this legacy file no longer exists on HEAD. Please port this change to the plugin adapter so it affects live Feishu delivery.
| def _capturing_adapter(): | ||
| """A FeishuAdapter whose message API records the last create/update request.""" | ||
| from gateway.config import PlatformConfig | ||
| from gateway.platforms.feishu import FeishuAdapter |
There was a problem hiding this comment.
Current tests import FeishuAdapter from plugins.platforms.feishu.adapter; update this helper and the added patch targets to the plugin module so the tests exercise the current runtime implementation.
| # renders it natively. Feishu added server-side table support to post 'md' | ||
| # elements, so the old force-text table downgrade is obsolete (verified on | ||
| # feishu.cn; see docs/plans/2026-05-30-feishu-markdown-table-rendering.md). | ||
| if _MARKDOWN_TABLE_RE.search(content) or _MARKDOWN_HINT_RE.search(content): |
There was a problem hiding this comment.
This routes both Feishu and Lark through post, but the adapter supports a distinct _domain_name == "lark" path and the PR description leaves Lark table rendering unconfirmed. Validate that domain or retain text routing there before enabling this universally.
Summary
Markdown tables sent through the Feishu adapter arrived as raw
|---|text, and any message that contained a table had its other markdown stripped as well._build_outbound_payload(gateway/platforms/feishu.py:4310) routed any table-matching content straight to plain text — a workaround from before Feishu's postmdelements rendered tables server-side. This removes the downgrade so table content goes through the existingpost/tag:mdpipeline, and normalizes CRLF before detection so\r\ntables are matched.Change
_build_outbound_payload(feishu.py:4310): route content matching_MARKDOWN_TABLE_RE(:159) or_MARKDOWN_HINT_RE(:153) topostvia_build_markdown_post_payload; everything else totext. Previously the table-matching branch returnedtextdirectly._build_outbound_payload(feishu.py:4310):content = content.replace("\r\n", "\n").replace("\r", "\n")before detection —_MARKDOWN_TABLE_REand the post row-splitter match a literal\n, so a CRLF table was missed._MARKDOWN_HINT_RE(:153); the post→text fallback that triggers on acontent format of the post type is incorrectrejection (_POST_CONTENT_INVALID_RE,:165); the shared payload call insend(:1785) andedit_message(:1839).Behavior
text(raw|---|)post(tag:md)text, surrounding markdown strippedpost(tag:md)text(detection missed\r\n)post(normalized, thentag:md)postpost(unchanged)|(e.g.int | str)texttext(unchanged)Testing
uv run --extra dev --extra feishu python -m pytest tests/gateway/test_feishu.py— 206 passed. 6 failures inTestWebhookSecurity/ url-verification predate this change and reproduce onorigin/main. New tests:test_send_routes_markdown_table_to_post— a bare table sends aspost; pins the SDK call tocreate.test_edit_message_routes_markdown_table_to_post— the same seam routesedit_messageaspost; pins the call toupdate.test_outbound_keeps_post_for_prose_surrounding_a_table— mixed prose+table stayspost.test_outbound_normalizes_crlf_table_to_post— a\r\ntable routes topost.test_outbound_single_pipe_line_stays_plain_text— a lone pipe staystext.test_table_detection_ignores_horizontal_rule—_MARKDOWN_TABLE_REdoes not match a---rule.test_table_inside_code_fence_stays_a_single_post_row— a fenced table stays one verbatim row.The tests assert routing to
post; the server-side render is not exercised by CI and was checked manually on feishu.cn.Note
The post→text fallback triggers on a
content format of the post type is incorrectrejection; it does not cover a payload the API accepts but renders empty. Table rendering is confirmed on feishu.cn and unconfirmed on larksuite.com — there, a non-rendering table would display as an empty message rather than raw text. A_domain_name == "lark"guard can route the international domain totextif a check shows that case.Related
Other PRs targeting this area, by approach:
tag:md: fix(feishu): keep markdown tables in post payload so prose still renders #22047, fix(feishu): render markdown tables via post md tags instead of raw text #26108, fix(feishu): render markdown tables via post+tag:md instead of plain text #29552tablecomponent #12114, fix(feishu): route tables and code blocks to CardKit 2.0 with post/text fallback #23861, fix(feishu): render markdown tables as interactive card table components #25453, fix(feishu): render markdown tables via cards #28837, feat(feishu): support markdown tables via CardKit table element (JSON 2.0 schema) #31038, feat(feishu): Schema 2.0 markdown card for GFM tables, code blocks, and inline formatting #32488, fix(feishu): render markdown tables and code blocks via CardKit 2.0 cards #33310Fixes: #7310 #9549 #18704 #18756 #23938 #25452 #26658 #27529 #29245 #32607
Addresses: #21866
Refs: #6003 #6363 #16084 #19226 #21326 #26236 #27695 #7022 #21778