fix(slack): render markdown in post-and-edit stream fallback - #901
Merged
bensabic merged 1 commit intoSep 5, 2026
Merged
Conversation
The stream fallback passed its accumulated text to postMessage/editMessage
as a bare string, which toSlackPayload resolves to Slack's `text` field.
`text` renders legacy mrkdwn only, so every intermediate edit showed raw
`**`/`#` markdown to the user, and it caps at 4,000 characters instead of
`markdown_text`'s 12,000.
`getCommittableText()` already produces append-safe markdown, so wrapping it
as `{ markdown }` routes it through the same `markdown_text` field native
mode streams into. `markdown_text` needs no scope beyond `chat:write` and
carries no app-feature gate; it is only mutually exclusive with `text`/
`blocks`, and toSlackPayload emits exactly one field, so the conflict cannot
arise.
Signed-off-by: CamdenA21 <camden@sandstone.com>
Contributor
|
@CamdenA21 is attempting to deploy a commit to the Vercel Team on Vercel. A member of the Team first needs to authorize it. |
bensabic
approved these changes
Sep 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
SlackAdapter.stream()'s post-and-edit fallback passes its accumulated text topostMessage/editMessageas a bare string, whichSlackFormatConverter.toSlackPayloadresolves to Slack'stextfield:textrenders classic mrkdwn (*bold*) only, not the GFM the renderer emits — so every intermediate edit during a fallback stream shows the user raw**/#/backtick syntax. Only a caller-side final replacement recovers the formatting, and only for the last frame; every frame before it was wrong while it was on screen.It's also a materially smaller ceiling:
textonchat.updatecaps at 4,000 characters vs.markdown_text's 12,000, so fallback mode fails on long answers sooner than it needs to —msg_too_longmid-stream, independent of the formatting bug.StreamingMarkdownRenderer.getCommittableText()is already documented as text "safe for append-only streaming" — it holds back unclosed inline markers (**,*,~~,`,[) and unconfirmed table headers. And the renderer's own class doc is explicit about which side owns conversion: "Outputs markdown (not platform text). Format conversion still happens in the adapter's editMessage → renderPostable → fromAst pipeline." Native mode honors that; fallback mode ships the same output through the wrong field. Wrapping it as{ markdown: committable }routes it into the samemarkdown_textfield native mode streams into, viatoSlackPayload's existingmarkdownbranch — nothing new to build.Per Slack's reference for
chat.postMessageandchat.update,markdown_textneeds no scope beyond thechat:writethe adapter already holds and carries no app-feature gate. Its one constraint is mutual exclusivity withtext/blocks(markdown_text_conflict), andtoSlackPayloademits exactly one field per branch, so that conflict can't arise here.One behavior change worth naming: Slack documents that mobile notifications use
message.textfor block-based messages, and doesn't document how push previews are derived formarkdown_text. In practice we see no notification regression — our app already postsmarkdown_textin native mode and for final message replacements — but flagging it rather than leaving it to be discovered.How we hit this
A Slack Workflow Builder–authored message (posted as a bot, no real
event.user) leavesrecipient_user_idinvalid for native streaming, so the adapter drops into fallback from the first send and stays there for the whole answer. That root cause is separate and app-side — not part of this PR — but it's what made this reproducible for us. Note it isn't the only route in: onceswitchToFallback()latchesnativeStreamingBrokenon afeature_not_enabled/method_deprecated/unknown_methoderror, every subsequent stream on that adapter instance takes this path.Test plan
pnpm validate— 43/43 tasks pass (knip, lint, typecheck, test, build).pnpm test:workspace— 3,714 passed / 6 skipped, 102 files.packages/adapter-slack/src/index.test.ts:streams fallback updates through markdown, not plain text— asserts everypostMessage/editMessagecall in fallback mode receives{ markdown: <string> }rather than a bare string, and that markdown syntax survives to the last frame.native streaming fallbacktests read the posted payload to assert content; updated to read.markdownvia a smallmarkdownOfhelper. These are the only places in the suite that assumed the bare-string shape — worth knowing for anyone auditing the blast radius.Checklist
git commit -s)pnpm validatepasses@chat-adapter/slack)