release-go-daemons: send telegram notifications as escaped HTML - #7
Merged
Merged
Conversation
The notify step passed the GitHub-generated changelog to Telegram with format: markdown. Legacy Markdown has no escape mechanism, so any lone delimiter in the release notes breaks the send: Bad Request: can't parse entities: Can't find end of the entity starting at byte offset 505 Every "* " bullet contributes one unpaired asterisk, so an odd number of bullets aborts the send (any-sync-node v0.12.1, filenode v0.11.3, coordinator v0.10.2). An even number does not make it correct: the bullets are consumed as bold delimiters and the message renders mangled, which is what the releases that appeared to succeed actually did. Asterisks are also not the only trigger -- _ is the italic delimiter, so dependabot titles such as "Bump github.com/prometheus/client_golang" fail the same way. Compose the message in github-script instead: escape & < > and map the changelog to Telegram HTML, the only format where the input can be fully escaped. Release notes are passed via env rather than interpolated into the script, so a PR title cannot execute as JavaScript. The body is capped at 3500 chars, since long changelogs otherwise exceed Telegram's 4096 limit and fail a different way. Note that format: markdown also silently disabled the action's own escaper, which is gated on the capitalised "Markdown"; that escaper only handles _ and never *, so it would not have fixed this on its own.
mighty-sponge
approved these changes
Jul 17, 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 subscribe to this conversation on GitHub.
Already have an account?
Sign in.
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.
Problem
The notify step sends the GitHub-generated changelog to Telegram with
format: markdown. Legacy Markdown has no escape mechanism, so any lone delimiter in the release notes breaks the send:Reconstructing the failing message from
any-sync-nodev0.12.1 puts byte 505 exactly on an asterisk — the closing one of**Full Changelog**, 13 total (odd).Each
*bullet contributes one unpaired asterisk, so it's a coin flip on changelog length. This predates the recent retagging: node v0.11.2, filenode v0.11.3, coordinator v0.10.2 all failed the same way.Two things that make this worse than it looks
The releases that "passed" were also broken. With an even count Telegram pairs the asterisks up and renders the message wrong — bullets consumed as bold delimiters, the first entry bolded,
**Full Changelog**losing its bold entirely. Parity only decides whether it fails loudly or mangles silently.Asterisks aren't the only trigger.
_is the italic delimiter, and real changelogs here containclient_golangandstart_redis, so every dependabot PR title is an independent trigger.The root cause is therefore not bullet parity but unescaped arbitrary text in a markup-formatted message.
The one-character fix that doesn't work
appleboy/telegram-actionhas a built-in escaper, but it's gated onp.Config.Format == "Markdown"(capital M) — lowercasemarkdownsilently skipped it. Fixing only the capitalisation looks like the answer, butescapeMarkdownOneonly escapes_and never touches*. It would fix the dependabot vector and leave this bug intact.Fix
Compose the message in
github-script: escape& < >, map##→bold,**x**→bold,*→•, and send asHTML— the only format where the input can be fully escaped.envrather than${{ }}interpolation, so a PR title can't execute as JavaScript.format: HTMLuses the canonical Bot APIparse_mode— the action forwards the value verbatim and case-insensitivity there is undocumented.Verification
The script was extracted from the YAML as shipped and executed against real and hostile inputs, validated against Telegram's HTML-mode rules (10/10 pass):
The validator was itself checked to reject all six failure classes (bare
<, bare&, unclosed tag, disallowed tag, mismatched nesting, >4096) so it isn't passing vacuously.What Telegram now receives for the release that broke prod:
Note
Consumers pin
release-go-daemons-reusable.yml@v1, so thev1tag needs moving after merge for this to take effect.