Skip to content

Shared parse_markdown: multi-backtick code spans, paragraph leading space, quadratic inputs (found in #283) #308

Open

Description

@patrick-chinchill

Summary

The shared parse_markdown (src/chat_sdk/shared/markdown_parser.py) has gaps that affect every adapter's inbound text. #283 recorded them while porting the Slack mrkdwn normalization, where every inbound Slack message passes through SlackFormatConverter.to_ast. None of them are Slack-specific, and none were introduced by #283.

Gaps

  1. No multi-backtick code spans. The inline pattern only matches `x`. Remark (upstream) reads ```c``` and ``a`b`` as code spans.
  2. A paragraph's leading space is kept. CommonMark strips it. "```x``` > note" gives "x\n\n > note", where upstream gives "x\n\n> note". Affected test: test_keeps_trailing_text_after_a_code_block_as_a_paragraph, which is adapted.
  3. Quadratic time on some inputs. 10k characters of `x take about 0.6 s, 20k characters about 2.4 s, and 10k characters of *a about 1 s. The issue text for [4.41/SL4b] Slack inbound mrkdwn normalization, channel.post thread ids, Socket Mode retries (remainder of #209) #283 measured 25–37 s at 50k characters. slack_mrkdwn_to_markdown itself stays linear (about 1 ms for the same inputs). The cost is in parse_markdown, which runs on every inbound Slack message, and on other adapters' inbound text.

Already fixed in #283

Acceptance

  • Remove the two adaptations in tests/test_slack_format.py, and match upstream's exact toPlainText values.
  • Add a linear-time regression test, asserting output on inputs of 50k characters or more.

Links: #283, #209, #184.

Activity

  1. patrick-chinchill commented on Oct 1, 2026

    @patrick-chinchill
    CollaboratorAuthor

    One more gap found while reviewing #307. Nested lists recurse once per level in parse_markdown, so "1. " * 1100 + "hi" and "- " * 1100 + "hi" raise RecursionError. This was already reachable on main, for example from Slack inbound text, which does not escape list markers. #307 caps only blockquote nesting, at 768 levels, because > now unescapes. A similar cap, or an iterative parser, would close the list case.

  2. patrick-chinchill commented on Oct 1, 2026

    @patrick-chinchill
    CollaboratorAuthor

    More findings from the #307 review round (PR head 868106e):

    Fixed in #307 (no longer part of this issue): gap 2, the paragraph leading space. parse_markdown now drops each paragraph line's leading spaces/tabs and the paragraph's trailing ones. Also fixed there: mixed-marker thematic breaks (-_-), intraword _ emphasis, and nested links in link text.

    New gaps for this issue:
    4. No lazy blockquote continuation. "> quoted\nreply" parses as a blockquote followed by a separate paragraph. Remark puts reply inside the quote paragraph. Slack example: ok\n> quoted\nreply gives message.text "ok\n\nquoted\n\nreply", where upstream gives "ok\n\nquoted\nreply".
    5. \r is not a line ending. a <!here\r> b gives "a <!here\r> b @here". Remark splits on \r, which gives "a <!here\n\nb @here c" (its > opens a quote).
    6. New quadratic triggers from the #307 fence rule. Before #307, a line starting with ``` always opened a code block that was parsed in O(n). Now such a line is inline text when its info string contains a backtick, and goes through the quadratic inline parser:

    • parse_markdown("```" + "a" * 8000)(16 KB) takes about 1.6 s.main` takes about 0 s.
    • parse_markdown("```x\n" * 8000)(48 KB) takes about 10 s.main` takes about 0.003 s.

    This is the same per-byte cost as the existing `x trigger, but it is reached through new inputs. A possible fix: in _parse_inline, cache each pattern's next match by absolute position instead of re-searching every remaining slice. Match at the cursor with a lookbehind-free variant of the pattern, so the semantics at the slice start stay the same.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions