Feishu markdown table rendering approaches and fixes
NousResearch/hermes-agent · cluster 6003 · gateway
Data as of
18 open items. Fix merged in #20275. Check it covers your case.
The short answer
- Known?
- Yes. 92 related items grouped here, 32 issues already closed.
- Fix event
- A fix was merged to main in PR #20275. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PRs #68122, #109140. That is separate from the fix event. - Still open
- 9 issues and 9 PRs.
Next step: read #20275, then check whether your report is one of the 9 still open.
Read #20275Pick up a Lab taskThis group centers on a single core problem: Feishu/Lark does not natively render markdown tables, causing raw source to appear in messages. The members represent the bug reports and multiple competing implementation attempts (post type, CardKit 2.0, interactive cards, native table components) to address this, along with related Feishu formatting and platform support work.
Open issues9
- Issue#6003[Feature]: [Feishu] Support Card 2.0 for richer message rendering (e.g. tables)
- Issue#19226[Feature]: use feishu card message to render the repsponse
- Issue#21326Feature Request: Support Card JSON 2.0 Tables in Feishu Adapter
- Issue#21873[Feature]: Native Feishu Interactive Card Support
- Issue#24319Feishu replies render raw Markdown while normal messages render correctly
- Issue#37777[Feature] Support Feishu interactive card (msg_type=interactive) in send_message
- Issue#46187feat(feishu): support interactive card messages in send_message for table rendering
- Issue#54950Feishu adapter: add renderMode config option for automatic card message wrapping
- Issue#54951Feishu/Lark adapter: add renderMode config option for automatic card messages
Open PRs9
- PR#6015feat(feishu): send markdown responses as Card 2.0 instead of post messages
- PR#10801fix(feishu): WebSocket watchdog + approval card PATCH + markdown table rendering
- PR#19231feat(feishu): use card to show complex message
- PR#22272fix(feishu): render markdown tables as native card tables
- PR#32488feat(feishu): Schema 2.0 markdown card for GFM tables, code blocks, and inline formatting
- PR#37830feat(gateway): support Feishu interactive card (msg_type=interactive) in send_message
- PR#40445feat(feishu): render markdown tables as native Card JSON 2.0 table components
- PR#48807feat(feishu): render markdown tables as CardKit v2 native table components
- PR#51739fix(plugins/feishu): render markdown tables as CardKit v2 interactive cards
Fixes on record
-
#20275 fix(feishu): force text mode for markdown tables (salvage #13723)
files:
gateway/platforms/feishu.pyShow the diff
-
gateway/platforms/feishu.py@@ -153,6 +153,9 @@ r"(^#{1,6}\s)|(^\s*[-*]\s)|(^\s*\d+\.\s)|(^\s*---+\s*$)|(```)|(`[^`\n]+`)|(\*\*[^*\n].+?\*\*)|(~~[^~\n].+?~~)|(<u>.+?</u>)|(\*[^*\n]+\*)|(\[[^\]]+\]\([^)]+\))|(^>\s)", re.MULTILINE, ) +# Detect markdown tables: a line starting with | followed by a separator line. +# Feishu post-type 'md' elements do not render tables, so we force text mode. +_MARKDOWN_TABLE_RE = re.compile(r"^\|.*\|\n\|[-|: ]+\|", re.MULTILINE) _MARKDOWN_LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)") _MARKDOWN_FENCE_OPEN_RE = re.compile(r"^```([^\n`]*)\s*$") _MARKDOWN_FENCE_CLOSE_RE = re.compile(r"^```\s*$") @@ -3995,6 +3998,12 @@ def _is_duplicate(self, message_id: str) -> bool: # ========================================================================= def _build_outbound_payload(self, content: str) -> tuple[str, str]: + # Feishu post-type 'md' elements do not render markdown tables; sending + # table content as post causes the message to appear blank on the client. + # Force plain text for anything that looks like a markdown table. + if _MARKDOWN_TABLE_RE.search(content): + text_payload = {"text": content} + return "text", json.dumps(text_payload, ensure_ascii=False) if _MARKDOWN_HINT_RE.search(content): return "post", _build_markdown_post_payload(content) text_payload = {"text": content}
-
-
#68121 fix(feishu): render markdown tables via post+md, remove force-text downgrade
files:
contributors/emails/[redacted:email],contributors/emails/[redacted:email],plugins/platforms/feishu/adapter.py,tests/gateway/test_feishu.py,tests/gateway/test_feishu_table_markdown.pyShow the diff
-
contributors/emails/[redacted:email]@@ -0,0 +1 @@ +M1racleShih -
contributors/emails/[redacted:email]@@ -0,0 +1 @@ +JasonFang1993 -
plugins/platforms/feishu/adapter.py@@ -152,11 +152,24 @@ # --------------------------------------------------------------------------- _MARKDOWN_HINT_RE = re.compile( - r"(^#{1,6}\s)|(^\s*[-*]\s)|(^\s*\d+\.\s)|(^\s*---+\s*$)|(```)|(`[^`\n]+`)|(\*\*[^*\n].+?\*\*)|(~~[^~\n].+?~~)|(<u>.+?</u>)|(\*[^*\n]+\*)|(\[[^\]]+\]\([^)]+\))|(^>\s)", + # Pipe table: any header line + separator line both starting with '|'. + r"(^\|.*\|\s*\n\|[-:|\s]+\|)" + # Headings, lists, code, bold/italic/strike/underline, links, blockquotes. + r"|(^#{1,6}\s)" + r"|(^\s*[-*]\s)" + r"|(^\s*\d+\.\s)" + r"|(^\s*---+\s*$)" + r"|(```)" + r"|(`[^`\n]+`)" + r"|(\*\*[^*\n].+?\*\*)" + r"|(~~[^~\n].+?~~)" + r"|(<u>.+?</u>)" + r"|(\*[^*\n]+\*)" + r"|(\[[^\]]+\]\([^)]+\))" + r"|(^>\s)", re.MULTILINE, ) -# Detect markdown tables: a line starting with | followed by a separator line. -# Feishu post-type 'md' elements do not render tables, so we force text mode. +# Backwards-compatible alias retained because external callers reference it. _MARKDOWN_TABLE_RE = re.compile(r"^\|.*\|\n\|[-|: ]+\|", re.MULTILINE) _MARKDOWN_LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)") _MARKDOWN_FENCE_OPEN_RE = re.compile(r"^```([^\n`]*)\s*$") @@ -4533,12 +4546,12 @@ def _is_duplicate(self, message_id: str) -> bool: # ========================================================================= def _build_outbound_payload(self, content: str) -> tuple[str, str]: - # Feishu post-type 'md' elements do not render markdown tables; sending - # table content as post causes the message to appear blank on the client. - # Force plain text for anything that looks like a markdown table. - if _MARKDOWN_TABLE_RE.search(content): - text_payload = {"text": content} - return "text", json.dumps(text_payload, ensure_ascii=False) + # Empirically (issue #52786), current Feishu clients render markdown + # tables inside ``post``-type ``md`` elements natively. The previous + # table-downgrade branch forced any table-containing message to + # ``text``, which left Feishu readers seeing the raw pipe-and-dash + # source instead of a rendered table. Trust the common markdown path + # for table content too. if _MARKDOWN_HINT_RE.search(content): return "post", _build_markdown_post_payload(content) text_payload = {"text": content} -
tests/gateway/test_feishu.py@@ -2690,6 +2690,23 @@ def test_build_post_payload_keeps_full_markdown_text(self): [[{"tag": "md", "text": "---\n1. 第一项\n 2. 子项\n- 外层\n - 内层\n<u>下划线</u> 和 ~~删除线~~"}]], ) + @patch.dict(os.environ, {}, clear=True) + def test_build_outbound_payload_uses_post_for_markdown_table(self): + from gateway.config import PlatformConfig + from plugins.platforms.feishu.adapter import FeishuAdapter + + adapter = FeishuAdapter(PlatformConfig()) + content = "| Name | Score |\n| --- | ---: |\n| Ada | 10 |" + + msg_type, raw_payload = adapter._build_outbound_payload(content) + + self.assertEqual(msg_type, "post") + payload = json.loads(raw_payload) + self.assertEqual( + payload["zh_cn"]["content"], + [[{"tag": "md", "text": content}]], + ) + @patch.dict(os.environ, {}, clear=True) def test_send_uses_post_for_inline_markdown(self): from gateway.config import PlatformConfig -
tests/gateway/test_feishu_table_markdown.py@@ -0,0 +1,133 @@ +"""Tests for Feishu adapter outbound markdown payload construction. + +Reproduces the bug tracked in hermes-agent issue #52786: +`_build_outbound_payload` was force-downgrading any message containing a +markdown pipe table to ``msg_type=text``, so Feishu clients rendered the raw +pipe-and-dash source instead of a table. Empirically current Feishu clients +render ``post``+``md`` tables natively, so the downgrade branch must be removed. + +These tests guard the fix. They invoke the real adapter via the project's +plugin-loader helper so that no ``sys.path`` / ``sys.modules`` games are +needed. +""" + +from __future__ import annotations + +import json + +from tests.gateway._plugin_adapter_loader import load_plugin_adapter + +_adapter = load_plugin_adapter("feishu") + + +def _call_build_outbound_payload(content: str) -> tuple[str, str]: + """Invoke ``_build_outbound_payload`` on a bare adapter instance. + + ``_build_outbound_payload`` is a method that only uses module-level + helpers (``_MARKDOWN_TABLE_RE``, ``_MARKDOWN_HINT_RE``, + ``_build_markdown_post_payload``) and never touches ``self.*``, so a bare + object is sufficient. + """ + inst = object.__new__(_adapter.FeishuAdapter) + return inst._build_outbound_payload(content) + + +def _md_texts_from_post_payload(payload_str: str) -> list[str]: + """Pull every ``{tag:'md', text:'...'}`` element out of a Feishu post payload. + + Real payload shape:: + + {"zh_cn": {"content": [[{"tag": "md", "text": "..."}], ...]}} + + Helpers and tests need to introspect the ``md`` blocks regardless of + locale, so we walk the structure generically. + """ + payload = json.loads(payload_str) + if not isinstance(payload, dict): + return [] + texts: list[str] = [] + for lang_val in payload.values(): + if not isinstance(lang_val, dict): + continue + content = lang_val.get("content", []) + if not isinstance(content, list): + continue + for block in content: + if isinstance(block, list): + candidates = block + else: + candidates = [block] + for el in candidates: + if isinstance(el, dict) and el.get("tag") == "md": + texts.append(el.get("text", "")) + return texts + + +def test_markdown_table_uses_post_not_text(): + """Regression test for issue #52786 (and its older sibling #23938). + + A message whose only markdown is a table must take the ``post`` path, + not be downgraded to plain text. + """ + content = ( + "| col A | col B |\n" + "| ----- | ----- |\n" + "| 1 | 2 |" + ) + msg_type, payload_str = _call_build_outbound_payload(content) + assert msg_type == "post", ( + f"expected 'post' for a markdown table (issue #52786), got {msg_type!r}; " + "the table-downgrade branch in _build_outbound_payload has been re-introduced" + ) + md_texts = _md_texts_from_post_payload(payload_str) + assert md_texts, f"post payload must include at least one md element; got {payload_str!r}" + joined = "".join(md_texts) + assert "col A" in joined and "|" in joined, ( + "table text was lost or reformatted when switching from text to post" + ) + + +def test_plain_text_without_markdown_still_uses_text(): + """Negative control: a message with no markdown hints and no table must + still go to plain text. Guards against accidentally promoting everything + to ``post``.""" + msg_type, _ = _call_build_outbound_payload("just a plain sentence with no markup") + assert msg_type == "text" + + +def test_existing_markdown_heading_still_uses_post(): + """Sanity: the existing ``post`` path (heading / list / code / bold / + link) must still work after the table downgrade is removed.""" + msg_type, payload_str = _call_build_outbound_payload("# hello world\n") + assert msg_type == "post" + md_texts = _md_texts_from_post_payload(payload_str) + assert md_texts, f"expected at least one md element; got {payload_str!r}" + assert any("hello world" in t for t in md_texts), ( + f"expected 'hello world' in md elements; got {md_texts!r}" + ) + + +def test_table_combined_with_other_markdown_does_not_downgrade(): + """A message that mixes a table with surrounding markdown must also + take the ``post`` path. + + The old ``_MARKDOWN_TABLE_RE`` branch returned ``text`` unconditionally + and stripped all the surrounding markdown formatting, so a Feishu + reader saw literal pipes and lost the prose framing the table. + """ + content = ( + "Here is the data:\n\n" + "| col A | col B |\n" + "| ----- | ----- |\n" + "| 1 | 2 |\n\n" + "Let me know." + ) + msg_type, payload_str = _call_build_outbound_payload(content) + assert msg_type == "post" + md_texts = _md_texts_from_post_payload(payload_str) + joined = "\n".join(md_texts) + assert "Here is the data" in joined, ( + "leading prose was lost when downgrading a mixed-table message" + ) + assert "col A" in joined, "table header was lost" + assert "Let me know" in joined, "trailing prose was lost"
-
-
#68122 fix(feishu): keep msg_type=post consistent across chunks of long markdown replies
files:
plugins/platforms/feishu/adapter.py,tests/gateway/test_feishu.pyShow the diff
-
plugins/platforms/feishu/adapter.py@@ -1910,11 +1910,21 @@ async def send( formatted = self.format_message(content) chunks = self.truncate_message(formatted, self.MAX_MESSAGE_LENGTH) + # When chunking splits a long markdown response, an individual chunk + # can end up as plain prose that doesn't match the per-chunk hint + # regex — so it would be sent as ``msg_type=text`` and the user would + # see literal ``**bold``/``## heading``/code fences in the Feishu + # client while other chunks render correctly. Lock the markdown + # decision at the whole-message level so every chunk consistently + # uses ``post``. See #26841. + prefer_post = bool(_MARKDOWN_HINT_RE.search(formatted)) last_response = None try: for chunk in chunks: - msg_type, payload = self._build_outbound_payload(chunk) + msg_type, payload = self._build_outbound_payload( + chunk, prefer_post=prefer_post, + ) try: response = await self._feishu_send_with_retry( chat_id=chat_id, @@ -4545,14 +4555,21 @@ def _is_duplicate(self, message_id: str) -> bool: # Outbound payload construction and send pipeline # ========================================================================= - def _build_outbound_payload(self, content: str) -> tuple[str, str]: + def _build_outbound_payload( + self, content: str, *, prefer_post: bool = False, + ) -> tuple[str, str]: # Empirically (issue #52786), current Feishu clients render markdown # tables inside ``post``-type ``md`` elements natively. The previous # table-downgrade branch forced any table-containing message to # ``text``, which left Feishu readers seeing the raw pipe-and-dash # source instead of a rendered table. Trust the common markdown path # for table content too. - if _MARKDOWN_HINT_RE.search(content): + # + # ``prefer_post`` lets ``send`` treat the chunk as part of a larger + # markdown document: when a long markdown reply is split at + # MAX_MESSAGE_LENGTH, the per-chunk regex would otherwise + # mis-classify a plain-prose chunk as ``text``. See #26841. + if prefer_post or _MARKDOWN_HINT_RE.search(content): return "post", _build_markdown_post_payload(content) text_payload = {"text": content} return "text", json.dumps(text_payload, ensure_ascii=False) -
tests/gateway/test_feishu.py@@ -2707,6 +2707,110 @@ def test_build_outbound_payload_uses_post_for_markdown_table(self): [[{"tag": "md", "text": content}]], ) + @patch.dict(os.environ, {}, clear=True) + def test_send_uses_post_for_every_chunk_of_multi_chunk_markdown(self): + """Regression for #26841: when a long Markdown message is split + across multiple chunks, every chunk must go out as + ``msg_type=post`` — including chunk 1. The bug was that the + first chunk often had only plain prose (the per-chunk regex + didn't match) and was sent as ``text``, so users saw literal + ``**bold``/``## heading``/code fences while later chunks + rendered correctly. + """ + from gateway.config import PlatformConfig + from plugins.platforms.feishu.adapter import FeishuAdapter + + adapter = FeishuAdapter(PlatformConfig()) + captured = [] + + class _MessageAPI: + def create(self, request): + captured.append(request) + return SimpleNamespace( + success=lambda: True, + data=SimpleNamespace( + message_id=f"om_chunk_{len(captured)}", + ), + ) + + adapter._client = SimpleNamespace( + im=SimpleNamespace( + v1=SimpleNamespace( + message=_MessageAPI(), + ) + ) + ) + + async def _direct(func, *args, **kwargs): + return func(*args, **kwargs) + + # Force a deterministic split so the test doesn't depend on the + # exact 8000-char limit. Chunk 1 is plain prose; chunk 2 has + # the markdown markers. Without the fix, chunk 1 went out as + # ``msg_type=text``. + first_chunk = "Here is a short intro that has no markdown markers at all." + second_chunk = "## Heading\nAnd then some **bold** text." + + with patch.object( + adapter, "truncate_message", return_value=[first_chunk, second_chunk], + ), patch("plugins.platforms.feishu.adapter.asyncio.to_thread", side_effect=_direct): + result = asyncio.run( + adapter.send( + chat_id="oc_chat", + content=first_chunk + "\n" + second_chunk, + ) + ) + + self.assertTrue(result.success) + self.assertEqual(len(captured), 2) + msg_types = [r.request_body.msg_type for r in captured] + self.assertEqual(msg_types, ["post", "post"]) + + @patch.dict(os.environ, {}, clear=True) + def test_send_plain_text_message_not_upgraded_by_prefer_post(self): + """A message with no markdown at all must still go out as plain + ``msg_type=text`` — the whole-message ``prefer_post`` decision + only flips on when the formatted message matches the hint regex. + """ + from gateway.config import PlatformConfig + from plugins.platforms.feishu.adapter import FeishuAdapter + + adapter = FeishuAdapter(PlatformConfig()) + captured = [] + + class _MessageAPI: + def create(self, request): + captured.append(request) + return SimpleNamespace( + success=lambda: True, + data=SimpleNamespace( + message_id=f"om_chunk_{len(captured)}", + ), + ) + + adapter._client = SimpleNamespace( + im=SimpleNamespace( + v1=SimpleNamespace( + message=_MessageAPI(), + ) + ) + ) + + async def _direct(func, *args, **kwargs): + return func(*args, **kwargs) + + with patch("plugins.platforms.feishu.adapter.asyncio.to_thread", side_effect=_direct): + result = asyncio.run( + adapter.send( + chat_id="oc_chat", + content="just a plain sentence", + ) + ) + + self.assertTrue(result.success) + self.assertEqual(len(captured), 1) + self.assertEqual(captured[0].request_body.msg_type, "text") + @patch.dict(os.environ, {}, clear=True) def test_send_uses_post_for_inline_markdown(self): from gateway.config import PlatformConfig
-
-
#109140 Feishu inbound post text is no longer markdown-escaped (#9816, salvage #9991)
files:
plugins/platforms/feishu/adapter.py,tests/gateway/test_feishu.py
38 PRs closed without merging
From the close comments: merged via another change 11 · done elsewhere 10 · superseded by another PR 8 · duplicate 5 · closed without comment 3 · other 1
- #7085 feat(feishu): send markdown responses as Card 2.0 instead of post messages · closed without comment
-
#9991 fix: remove excessive markdown escaping in Feishu text element rendering · merged via another change →
d131391,#109140 -
#12114 fix(feishu): render markdown tables via card v2 `table` component · done elsewhere →
#68121 -
#16474 fix(feishu): unify outbound to post and degrade tables to text element · done elsewhere →
#68121 -
#19015 fix(feishu): wrap pipe tables in code fences so columns survive · done elsewhere →
8e18d10,#18756
Show 33 more
-
#19038 fix(feishu): route tables and multi-line code blocks to CardKit 2.0 · done elsewhere →
#20275,#13723,#19035,#9549 -
#22047 fix(feishu): keep markdown tables in post payload so prose still renders · merged via another change →
#68121,#58019,#29552 - #22259 fix(feishu): wrap markdown tables in code_block tags for post rendering · done elsewhere
- #23861 fix(feishu): route tables and code blocks to CardKit 2.0 with post/text fallback · done elsewhere
-
#25453 fix(feishu): render markdown tables as interactive card table components · superseded by another PR →
#46472 - #26108 fix(feishu): render markdown tables via post md tags instead of raw text · done elsewhere
-
#26119 fix(feishu): render markdown tables via rich post type instead of plain text · duplicate →
#26108 -
#26848 fix(feishu): send every chunk of a long markdown reply as msg_type=post (#26841) · merged via another change →
#68122,#68121 -
#27400 fix(feishu): remove markdown table force-text workaround, add pipe table to post hint (#26658) · superseded by another PR →
#27922 -
#27739 fix(feishu): render markdown tables via post+md instead of force-text · duplicate →
#26108,#27469 -
#28837 fix(feishu): render markdown tables via cards · done elsewhere →
977884e,#68121,#58019 -
#29472 fix(feishu): use consistent msg_type across all message chunks · duplicate →
#26848,#16474,#18371 - #29510 fix(feishu): convert markdown tables to bullet lists instead of raw text · closed without comment
-
#29552 fix(feishu): render markdown tables via post+tag:md instead of plain text · merged via another change →
#68121 -
#31038 feat(feishu): support markdown tables via CardKit table element (JSON 2.0 schema) · done elsewhere →
977884e,#68121,#58019 -
#32648 fix(feishu): render markdown tables as aligned code blocks instead of raw text · other →
#27469,#9549,#12114,#21872,#20580,#32488 -
#33310 fix(feishu): render markdown tables and code blocks via CardKit 2.0 cards · superseded by another PR →
#53453,#53355,#36877 -
#33800 fix(feishu): render markdown tables via Schema 2.0 interactive cards · duplicate →
#26108,#29552,#31056,#31410,#9549 -
#38834 fix(feishu): remove outdated markdown table fallback, always use post format · merged via another change →
#68121,#58019,#29552 -
#38867 fix(feishu): remove outdated markdown table fallback, always use post format #38755 · duplicate →
#38834,#38755,#27469,#31410,#34181 -
#39955 fix(feishu): remove outdated markdown table text-mode fallback · merged via another change →
#68121,#58019,#29552 -
#43486 fix(feishu): stop over-escaping Markdown in _escape_markdown_text (#43437) · superseded by another PR →
#9816,#9991,#43437 -
#45583 fix(feishu): route markdown tables through post instead of broken interactive card · superseded by another PR →
#53453 -
#46472 fix(feishu): render all markdown via Card 2.0 interactive cards · superseded by another PR →
#68121,#25452 - #46502 fix(feishu): render markdown tables as interactive cards · closed without comment
-
#50640 fix(feishu): route markdown tables through post+md instead of forcing text · superseded by another PR →
#29166 -
#52066 fix(feishu): preserve markdown around tables · done elsewhere →
#68121,#52046 -
#52790 fix: send Feishu markdown tables as post · merged via another change →
#68121,#58019,#29552 -
#58019 fix(feishu): render markdown tables via post+md, not text downgrade · merged via another change →
#68121 -
#58391 fix(feishu): use post(md) for messages containing markdown tables · merged via another change →
#68121,#38834,#58019,#29552 -
#61647 fix(feishu): render Markdown tables natively instead of falling back to plain text (#61643) · merged via another change →
#68121,#58019,#29552 -
#65529 fix(feishu): remove outdated markdown table fallback, always use post format · merged via another change →
#68121,#58019,#29552 -
#79420 fix(feishu): render clarify prompts as interactive card buttons with text fallback · superseded by another PR →
#54304,#76274
Closed issues · 32
- Closed#7022[Feishu Bug] Markdown source code displayed instead of rendered card format
- Closed#7310## Problem When sending markdown table syntax (| col1 | col2 |) in Feishu, the message displays as plain text instead of being rendered as a table. This works correctly in OpenClaw. ## Environment - Hermes Agent version: [latest] - Feishu integration via WebSocket - Using native markdown table syntax in messages ## Expected behavior Markdown tables should render as formatted Feishu table messages, similar to how OpenClaw handles them. ## Screenshots [Attach screenshot comparing OpenClaw vs Hermes Agent rendering] ## Additional context The Feishu platform adapter uses `post` msg_type with markdown, but the markdown hint regex may not be detecting table syntax properly.
- Closed#9536fix(feishu): markdown tables not rendering as interactive cards — simplify `_should_send_as_card()` trigger condition
- Closed#9549[Feishu] Markdown tables not rendering in Feishu messages
- Closed#9816Feishu/Lark messages poorly formatted due to excessive Markdown escaping
- Closed#18704feishu: Markdown tables render as garbled text — missing post-format table support
- Closed#18756Feishu gateway: Markdown tables silently dropped in outgoing messages
- Closed#21778Bug Report: Feishu (Lark) Markdown Table Rendering Regression in v0.13.0
- Closed#21866Feishu: markdown tables render as raw text instead of using interactive card
- Closed#23938[Bug]: Feishu adapter strips all Markdown formatting when message contains a table
- Closed#25452Feishu channel displays markdown tables as raw source code instead of rendering them
- Closed#26078[Bug]: tables remain markdown source code in feishu chat window
- Closed#26174[Bug]: hermes返回的内容无法在飞书渲染
- Closed#26236[Feature]: Request Feishu support to return messages in the form of streaming cards, making the messages more aesthetically pleasing.
- Closed#26658[feishu] Remove markdown table force-text workaround — Feishu now supports tables in post format
- Closed#26841[Feishu] First chunk of long messages sent as msg_type=text instead of post, breaking Markdown rendering
- Closed#27469[feishu] Unified fix for markdown rendering: inbound escaping + GFM table → Card 2.0
- Closed#27529[feishu] Fix markdown table rendering: use post+tag:md instead of force-text workaround
- Closed#27692feat(feishu): support markdown tables via CardKit table element
- Closed#27695feat(feishu): support markdown tables via CardKit table element
- Closed#29245Feishu gateway: Markdown tables render as broken plain text
- Closed#29471bug(feishu): first chunk of long messages sent as msg_type=text, breaking Markdown rendering
- Closed#32607Feishu messages: markdown tables not rendered, making them unreadable
- Closed#38755fix(feishu): remove outdated markdown table fallback, always use post format
- Closed#43437Feishu/Lark: _escape_markdown_text over-escapes characters causing Markdown rendering corruption
- Closed#46470[feishu] Render all outbound markdown via Card 2.0 interactive cards (unified fix)
- Closed#50602Feishu adapter sends raw Markdown source for table-containing messages
- Closed#52046[Feishu] A markdown table downgrades the entire message to plain text, breaking all other formatting
- Closed#52786Feishu adapter incorrectly downgrades markdown tables to plain text
- Closed#56430Feishu 消息中 Markdown 表格被降级为纯文本导致渲染错误
- Closed#58269[feishu] Remove outdated table-to-text fallback — post(md) now supports GFM tables
- Closed#61643🐛 Bug: Feishu messages show raw Markdown table syntax instead of rendering
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.03: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the Lab