Skip to content

fix(slack): Convert canvas HTML to Markdown on read - #2647

Merged
thomasjiangcy merged 5 commits into
mainfrom
fix/canvas-read-html-to-markdown
Nov 4, 2026
Merged

thomasjiangcy merged 5 commits into
mainfrom
fix/canvas-read-html-to-markdown

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Canvases that Junior edits end up showing raw HTML. The TPM digest canvas F0CE40T1DUH is one example. Its body is now one paragraph of escaped <div class="quip-canvas-content"><h1 …>… text. The original document is nested inside that paragraph, and its heading ids match the outer heading.

Root cause: readCanvas downloads the canvas from url_private and returns the bytes unchanged. Slack serves that download as Quip HTML, not Markdown. slackCanvasRead says it returns Markdown, but it returns this HTML. slackCanvasEdit takes the HTML, applies the edit, and writes it back with document_content.type: "markdown". Slack then escapes all of it as literal text. So every edit on a real canvas breaks it. A slackCanvasWrite of content from slackCanvasRead breaks it the same way. The tests did not catch this because their fake download returned Markdown.

Fix: readCanvas converts an HTML body to Markdown with node-html-markdown, which is already a dependency. It also maps Quip's <lnk href> to a Markdown link. A body that is already Markdown does not change. Read and edit both go through readCanvas, so both now work on Markdown.

The Slack export also starts with the canvas title as an h1. Slack keeps that heading when the body is replaced. I checked this on F0CE40T1DUH, where the title heading kept the same id. So the read now removes a leading # <title>. Without this, every edit would show the title twice. The edit integration test now covers a Quip HTML body. It fails without this change.

Remaining risk: node-html-markdown escapes characters such as [ and .. I expect Slack's Markdown parser to read those escapes as plain characters, but I have not checked this against live Slack.

via @dingsdax.

--

View Session [Sentry]

@vercel

vercel Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
junior-docs Ready Ready Preview Nov 4, 2026 12:41pm UTC

Request Review

@blacksmith-sh

This comment has been minimized.

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

@sentry-junior

sentry-junior Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor Author

check / types and test / junior unit failed here because of a break on main, not this PR: web-batch-messages.test.ts imported webActorFromEmail, which #2630 removed (TS2305). #2643 fixed it on main as e35103a2d. Rebase onto main to clear those checks. ci / required failed only because those two did.

@thomasjiangcy

Copy link
Copy Markdown
Contributor

I think this is generally ready to go! Just need to rebase to pull in fixes for CI.

A small nit is that instead of checking .startsWith("<"), checking for actual HTML tags with regex may be better since the current approach can have false positives (though probably very unlikely with Slack canvases), so it's optional here.

Slack serves canvas downloads as Quip HTML. slackCanvasEdit wrote that
HTML back as Markdown, so Slack rendered the whole canvas as escaped
HTML text. Convert the body to Markdown in readCanvas so reads and
edits both work on Markdown.
Slack keeps the title h1 across a body replace, so an edit that wrote it
back would show the title twice.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 06a6efd. Configure here.

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

6 screenshot changes — 6 changed · 0 added · 0 removed

Review screenshots in Frameshift

Automation Page · Desktop
Automation Page · Desktop
Changed
Automation Run Details · Desktop
Automation Run Details · Desktop
Changed
Automation Run Details Light · Desktop
Automation Run Details Light · Desktop
Changed
Conversation Attachment · Desktop
Conversation Attachment · Desktop
Changed
Conversation Share Details · Desktop
Conversation Share Details · Desktop
Changed
Settings Mcp · Desktop
Settings Mcp · Desktop
Changed

@thomasjiangcy
thomasjiangcy merged commit 4a11074 into main Nov 4, 2026
46 checks passed
@thomasjiangcy
thomasjiangcy deleted the fix/canvas-read-html-to-markdown branch November 4, 2026 17:00
thomasjiangcy added a commit that referenced this pull request Nov 5, 2026
The `prepare-commit-msg` hook removes every `Co-authored-by` line, then
adds only the human actors of the current run. A run with no human actor
(PR event follow-ups, automations, child agents) adds nobody. So when
one of those runs amends or rebases, earlier credit is gone. That's how
#2647 lost dingsdax's credit: a `pull_request.comment.created` follow-up
rebased the branch and force-pushed, and none of the new commits had his
trailer.

The hook now builds co-authors from the commits being replaced, then
appends the current run's human actors. It finds those commits from Git
state:

- `--amend`, `-c`/`-C`, `reword`: the source commit. `--amend -m`/`-F`
give no commit arg, so the hook reads `--amend` from the parent `git
commit` cmdline.
- rebase `pick` and `cherry-pick`: `CHERRY_PICK_HEAD`. A conflicted
rebase step: `REBASE_HEAD`. An `edit` stop (`rebase-merge/amend`) that
makes a new commit gets no inherited credit.
- `fixup`/`squash`: HEAD plus the folded commit. Rebased merges: the
`merge -C` commit. `merge --squash`: the commits listed in `SQUASH_MSG`.

Model-written message text still can't add co-authors. Trailers are
deduped by email, and the bot is excluded.

Other bugs found while reproducing:

- The author check failed on an amend or `rebase --continue` of a commit
a person authored, because Git passes the original author to the hook.
It now accepts the author of a replaced commit only. A new commit with a
spoofed author still fails.
- A one-line unscoped subject like `chore: base` was read as a trailer
block, so `Co-Authored-By` got stuck right under the subject. The
subject paragraph is never trailers now, and a trailer key has to start
with a capital letter (or be `co-authored-by`), so `feat: x` lines in
squash messages stay body text.
- The comment block Git adds for an editor (conflicts, squash) broke
trailer detection. Trailers now go before it.
- The hook is POSIX `sh` now (passes shellcheck, tested under dash).
`-ef` is replaced with a `pwd -P` comparison.

Known limits:

- `rebase --apply` and `git am` don't run `prepare-commit-msg`.
- `-C <any sha>` reuses that commit's co-authors and author.
- The `--amend -m` detection depends on `/proc`, so it only works on
Linux (the sandbox).
- `core.hooksPath` overrides or `commit-tree` skip the hook. This is a
guardrail, not a security boundary.

Follow-up, not in this PR: runs with no human actor on a PR could credit
the PR's stored requester, so brand-new fixup commits get credit too.

<!-- junior-request-attribution:start -->
via @thomasjiangcy.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!--
junior-conversation-id:agent-dispatch%3Adispatch_2f266e56e30de8bcdda52e3d36433743
-->

--

[View
Session](https://junior-prod.sentry.dev/conversations/agent-dispatch%3Adispatch_2f266e56e30de8bcdda52e3d36433743)
[[Sentry]](https://sentry.sentry.io/explore/conversations/agent-dispatch%3Adispatch_2f266e56e30de8bcdda52e3d36433743/?project=4510944073809921)

<!-- junior-session-footer:end -->

---------

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: Thomas Jiang <20501235+thomasjiangcy@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview – junior-docs — e8020f29 Deployed Nov 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants