Repository navigation
fix(slack): inbound mrkdwn normalization, channel.post thread ids, Socket Mode retries (#283) - #307
Conversation
…ocess Socket Mode retries (#283) Part (b) of #209. Ports the Slack halves of vercel/chat 0b63791b (#667), 92530dd3 (#720), c3118279 (#756), e71bfead (#843) and 44423bdc (#960). - slack_mrkdwn_to_markdown: special mentions, #name (C...) channels, inverted links, Slack code fences on their own lines (linear scanners). - SlackFormatConverter.to_ast = parse_markdown(slack_mrkdwn_to_markdown()); drop the Python-only regex extract_plain_text override. - message.text = ast_to_plain_text(formatted) on both parse paths. - post_channel_message returns slack:{channel}:{ts} for a string ts. - Socket Mode retry envelopes are routed and logged instead of dropped.
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (15)
Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review. 📝 WalkthroughWalkthroughSlack inbound parsing now normalizes mrkdwn before shared Markdown parsing and derives plain text from the formatted AST. Channel posts use a timestamp-based thread ID when Slack returns a string timestamp. Socket Mode retries are routed for processing and duplicate-event checks. ChangesSlack message handling
Priority: ➖ Normal Estimated code review effort: 4 (Complex) | ~45 minutes Change: Bug fix Sequence Diagram(s)sequenceDiagram
participant SlackAdapter
participant SlackFormatConverter
participant slack_mrkdwn_to_markdown
participant parse_markdown
participant ast_to_plain_text
SlackAdapter->>SlackFormatConverter: to_ast(text)
SlackFormatConverter->>slack_mrkdwn_to_markdown: Normalize mrkdwn
slack_mrkdwn_to_markdown-->>SlackFormatConverter: Markdown
SlackFormatConverter->>parse_markdown: Parse normalized Markdown
parse_markdown-->>SlackFormatConverter: Formatted AST
SlackAdapter->>ast_to_plain_text: Derive text from formatted AST
Merge Risk: 🔵 Low · up to This change alters how Slack inbound text is derived, returns replyable thread IDs for channel posts, and routes Socket Mode retries. Automated validation reportedly passes. The live Slack-loop check is still pending, so owners should confirm it before or shortly after merge. Security Architecture ReviewSecurity architecture risk: 🟡 Moderate · up to Retry recovery improves delivery of missed events, but non-message callbacks have weaker replay protection than messages. Duplicate delivery or delivery-marker failures can repeat application handlers. The security impact depends on whether those handlers perform non-idempotent actions; no authorization bypass was established. Retained concerns
Security review detailsSecurity Blast Radius
Security Findings and Attack Paths
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
Full details: Out of Scope Changes checkExplanation The PR changes the shared Full details: Docstring CoverageExplanation Docstring coverage is 28.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 100 functions across 12 files. (3 skipped: 3 unsupported.)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. A rabbit read the Slack text stream, Comment |
…cktick (#283) Astra round 1: a Slack quote starting with an inline fence (> ```npm test```) parsed to an empty code block, dropping the command from message.text. CommonMark (remark upstream) forbids backticks in a backtick fence's info string. Also document the upstream-parity choices for unpaired fences and non-events_api socket retries.
# Conflicts: # CHANGELOG.md
…te depth (#283) Astra round 3: _is_on_blockquote_line rescanned the line per fence (quadratic on long indented quote lines); it now remembers the current line. Unescaped > runs reached the recursive blockquote parser and raised RecursionError; past 768 levels the rest stays literal text (divergence documented in docs/UPSTREAM_SYNC.md).
Astra round 4: the heading branch reused the depth variable and reset the blockquote nesting budget.
…room for lists (#283) Astra round 5: labelled Slack links with brackets in the label or parens in the URL leaked [..](..) syntax into message.text now that text comes from the AST; the link pattern accepts one level of balanced brackets and parens, as CommonMark/remark do. Quotes nested 700 deep plus nested lists overflowed the stack; the blockquote cap drops to 100 (markdown-it's default maxNesting).
Astra round 6: the balanced-paren destination treated an escaped \( as an opener, so [manual](https://example.com/a\(b) stopped being a link. Escape-sentinel pairs are consumed whole, as in the label pattern.
…traword _, no nested links; pin memo scanners and socket retry marker (#283) Addresses the two-reviewer round on #307: - CHANGELOG #268/#194 bullets no longer describe the pre-#283 behavior - link text may not contain a full inner link ([[a](u) b](v)) - thematic breaks repeat one marker (-_- is text) - _ emphasis is never intraword (snake_case + underscored URLs) - paragraphs drop leading/trailing spaces (inline-fence text) - exact-output tests for _BlockquoteLines/_AngleTokenScanner memo state - socket retry reaches the event-delivered marker (test) - docs: lazy-continuation and new quadratic triggers recorded under #308
|
Merge gate: CI green (Lint & Type Check, test (3.12), test (3.13), Analyze (python), Analyze (actions), CodeQL, CodeRabbit); local Codex review (gpt-6-astra, xhigh, --base origin/main) on 236ebf3: "No actionable regressions found against the specified merge base. The full test suite passed: 7,971 passed and 24 skipped"; 2 astra rounds (convergence; round 1 P2s fixed with tests); CodeRabbit APPROVED with no actionable comments, no gemini comments. origin/main is an ancestor of HEAD, so the reviewed code is the merged code. Merging with --admin (Protect Main requires a code-owner approval). |
Summary
Part (b) of #209: Slack inbound mrkdwn normalization,
channel.post()thread ids, and Socket Mode retries. It starts fromwip/4.41-sl4-full(c0788056) minus what #286 (part a) and #291 (#210) already merged. The format modules and their tests applied cleanly. The adapter hunks were re-applied by hand on currentmain.Upstream commits mapped
0b63791bfix(slack): process Socket Mode retry envelopes (#667), Slack half_on_socket_requestno longer acks and dropsretry_attempt > 0. It logs"Processing socket mode retry"at info (retry_attempt,retry_reason,type) and routes the envelope like a first delivery.retry_numreaches the #268 event-id marker.92530dd3fix(slack): return replyable thread ID from channel posts (#720)post_channel_messagereturnsslack:{channel}:{ts}whenraw["ts"]is astr, and otherwise the syntheticslack:{channel}:.c3118279fix(slack): preserve channel id, normalize hallucinated link format (#756)<#C1|gen>becomes#gen (C1). The inverted link<label|https://…>is swapped back.e71bfeadfix(slack): preserve first line of incoming code blocks (#843), Slack half_convert_mrkdwn_with_code_fencesand its helpers (_find_inline_code_end,_is_on_blockquote_line,_escape_leading_block_marker).44423bdcfix(slack): convert special mentions to readable text (#960)_convert_special_mentionsand_AngleTokenScanner(upstreamfindAngleTokenEnd).Also:
SlackFormatConverter.to_astis nowparse_markdown(slack_mrkdwn_to_markdown(text)), as upstreamtoAst. This fixes a missing unescape:<,>and&used to reachformattedandtextverbatim.extract_plain_textoverride is removed. Upstreammarkdown.tshas no such override._assemble_contentreturnsast_to_plain_text(formatted)(upstreamtext: toPlainText(formatted)). This replaces the [4.41/SL5] Slack inbound: pasted tables and alert attachments in message content #210 interim and its four interim assertions.Tests ported
tests/test_slack_format_primitives.py: the 10e71bfeadformat tests, the 7 from44423bdc(including theit.each, as a parametrized test), the 3 fromc3118279, the updated "normalizes Slack mrkdwn to Markdown" test, and a Python adversarial case with 50k characters (unclosed<runs, unbalanced backticks, quoted fences). The adversarial case asserts output, not timing.tests/test_slack_format.py: the 6e71bfeadmarkdown.test.tscases, plus a Python test thatto_astunescapes entities.tests/test_slack_inbound_mentions.py: "converts special mentions to readable text", "preserves special mention tokens in an inbound inline code span", "preserves special mention tokens in an inbound code block", "posts to channel without thread context", and a Python case where the response has no stringts.tests/test_slack_socket_mode.py: "processes retries like first deliveries (dedupe drops true duplicates)" replacestest_retry_attempt_is_skipped_but_acked.tests/test_slack_inbound_content.py: three [4.41/SL5] Slack inbound: pasted tables and alert attachments in message content #210 interim assertions now expect upstream's values (#C789 @S789 …,Deploy failed:\n\nTypeError: boom…and#general (C789)). The two pre-[4.41/SL4b] Slack inbound mrkdwn normalization, channel.post thread ids, Socket Mode retries (remainder of #209) #283 guards are replaced bytest_body_text_is_the_plain_text_of_formatted.I also differential-fuzzed
slack_mrkdwn_to_markdownagainst upstreamformat/index.ts4.41.1 run under node: 40,000 random inputs built from mrkdwn atoms, with 0 mismatches.Fidelity
Delta vs committed report (HEAD): missing 100 -> 100 (+0). The ported files are adapter tests, which are outsideMAPPINGandTARGET_MAPPING.fidelity_target.jsonchanges only in its extra-test count (Python-only parser tests intest_markdown_faithful.py; the convergence round re-ran it and the delta stayedmissing 100 -> 100 (+0)). Strict at 4.31.0 passes ("All TS tests have Python equivalents"; 730 real tests plus 3 absorbers).Divergences
There is one divergence: the
parse_markdownblockquote depth cap described above. It has a row in the non-parity table, a breadcrumb at the code site, a regression test and a CHANGELOG entry. Two Python-specific details, both documented indocs/UPSTREAM_SYNC.mdunder "Slack inbound mrkdwn, channel-post ids, Socket Mode retries":retry_attempt, which is slack_sdk's name for upstream'sretry_num.<…>scan is memoized, the newline search is bounded, and the blockquote check matches in place. Their results are identical to upstream.Shared
parse_markdownfixes (prompted by astra). Each one closes a way the AST-derivedmessage.textlost or garbled content:> ```npm test````) parsed to an empty code block, and the command was dropped frommessage.text. With the fix it stays text. This is a one-line regex change in_FENCED_CODE_START_RE` and affects every adapter, but only on such lines.>now unescapes, so">" * 1100raisedRecursionErrorin the recursive blockquote parser. That dropped the message, or a wholefetch_messagespage. Past 100 levels (markdown-it's defaultmaxNesting) the rest of the quote stays literal text, which leaves stack room for lists nested inside quotes. The nesting depth is a separate variable from the heading level. The existing [4.41/C2b] Shared text utilities: bare-mention scanner, code-fence helpers, to_plain_text structural whitespace #193 test that extraction handles a 600-deep AST now builds that AST directly.<https://example.com|build [failed]>readsbuild [failed], and…/Foo_(bar)keeps its). Escaped\(and\)are consumed whole. A differential run againstmain's parser (60k random link-ish inputs) differs only on malformed inputs, wheremainbuilt broken URLs such as\.Also from round 3: the blockquote-line check in the fence normalizer now remembers the current line (
_BlockquoteLines), so a long indented quote line full of fences stays linear.Known shared-parser gaps (not Slack-specific, and not caused by this PR), now tracked in #308:
parse_markdownhas no multi-backtick code spans and keeps a paragraph's leading space. Twomarkdown.test.tsassertions are adapted because of this.parse_markdownis quadratic on some inputs, about 2.4 s for 20k characters of`x.to_astalready ranparse_markdownon every inbound message before this change.Consumer impact (Slack, high)
message.textchanges. It is now the plain text ofmessage.formatted. List markers, heading#, quote>and inline-code backticks are dropped. A trailing newline is trimmed. Blocks are joined by a blank line.<!here>,<!channel>and<!everyone>become@here,@channeland@everyone.<!subteam^S1|@eng>becomes@eng, and<!subteam^S1>becomes@S1.<#C123|general>becomes#general (C123). Tokens inside code stay literal.<,>and&are unescaped. This also applies to table cells and mrkdwn attachment parts.```npm test```gave an emptyformatted. Now it gives acodenode, andtextis"npm test".channel.post()returnsslack:C123:<ts>(replyable) instead ofslack:C123:. File-only posts keepslack:C123:.on_messageregexes that matched#channel-name, raw<!here>, list markers or backticks. Match onmessage.formattedormessage.raw["text"]when you need the structure.A live Slack-loop check is pending (DM and channel: a code block on its first line,
<!here>,#channel, achannel.post()reply in its thread, and a socket reconnect retry).Astra review
events_apisocket retries are not deduped. This matches upstream:routeSocketEventconsults the marker only forevents_api(adapter-slack index.ts:3087-3135). I added a code comment and no new machinery.```npm testgives an empty code block. Upstream's remark does the same, so this is parity; I added a code comment.```npm test```lost its content because of a Python parser bug. It is fixed by the CommonMark fence rule above, with a regression test.main): RecursionError on deep>nesting, and the quadratic blockquote-line check. Both fixed.[]or()leaked syntax into the text. Fixed (cap of 100, link pattern).\(in a link destination. Fixed.Validation
ruff check, ruff format --check, audit_test_quality (0 hard failures),
verify_test_fidelity --check-docs,--strictat 4.31.0, pytest (7971 passed, 24 skipped at 236ebf3), and pyrefly (0 errors).Merge gate
Final HEAD:
236ebf3.Review round (two independent reviewers, 9 findings). All 9 were fixed; none were declined. Each fix has a test that fails without it. Parser fixes were checked against remark-parse + remark-gfm
toPlainText.slack:C…:is reported only until [4.41/SL4b] Slack inbound mrkdwn normalization, channel.post thread ids, Socket Mode retries (remainder of #209) #283 or without a string ts).[[a](u) b](v)). A nested[...]may not be directly followed by(, so the inner link wins, as in remark.message.textgaps. Trailing whitespace is now fixed (see item 9). Lazy blockquote continuation,\rline endings and the new quadratic triggers are recorded in the docs/UPSTREAM_SYNC.md known gaps, the CHANGELOG and Shared parse_markdown: multi-backtick code spans, paragraph leading space, quadratic inputs (found in #283) #308 (comment).-_-gave an emptymessage.text. A thematic break must now repeat one marker (CommonMark)._emphasis is never intraword now, somy_varand?utm_source=keep their underscores.test_memoized_scanners_match_upstream_across_lines(8 exact upstream outputs). All 5 reported mutants are now killed.`xtrigger onmain, and the inline-parser rewrite belongs to Shared parse_markdown: multi-backtick code spans, paragraph leading space, quadratic inputs (found in #283) #308.test_socket_retry_consults_the_event_delivered_marker(seeded marker gives ack without dispatch; no marker gives dispatch). It kills theretry_num=0mutant.parse_markdownnow strips each paragraph line's leading spaces/tabs and the paragraph's trailing ones, as CommonMark does. Therun ```npm test``` pleasecase is restored. The Shared parse_markdown: multi-backtick code spans, paragraph leading space, quadratic inputs (found in #283) #308 leading-space adaptation intest_slack_format.pyis removed, and the test now asserts upstream's exact value.gpt-6-astra (convergence): 2 rounds.
***thematic breaks: a trailing\ris allowed.[`[`](u)). The alternatives are mutually exclusive, so there is no backtracking blowup.Bots: CodeRabbit APPROVED
236ebf3with no actionable comments. There were no gemini comments.CI on
236ebf3: all checks green (Lint & Type Check, test 3.12/3.13, CodeQL).Local validation on
236ebf3: all green. ruff, format, audit (0 hard failures),--check-docs,--strict4.31.0, pytest (7971 passed), pyrefly (0 errors).Delta vs committed report (HEAD): missing 100 -> 100 (+0).Closes #283
Part of #184
Summary by CodeRabbit
New Features
Bug Fixes