Skip to content

fix(notifications): escape and truncate Telegram build-error messages, surface API rejections - #5398

Closed
breken-ai wants to merge 2 commits into
Dokploy:canaryfrom
breken-ai:fix-5392-telegram-build-error
Closed

breken-ai wants to merge 2 commits into
Dokploy:canaryfrom
breken-ai:fix-5392-telegram-build-error

Conversation

@breken-ai

@breken-ai breken-ai commented Sep 9, 2026 •

Copy link
Copy Markdown

Fixes #5392.

Telegram build-error notifications were silently dropped on any substantial build failure, for three stacked reasons:

  1. The error message was interpolated unescaped into a parse_mode=HTML payload - typical build output contains <, > and &, so Telegram rejected it with "can't parse entities".
  2. Unlike the Discord/Lark/Teams branches, the Telegram branch never truncated the error; anything past Telegram's 4096-character message limit was rejected with "message is too long".
  3. sendTelegramNotification never checked response.ok, and fetch doesn't throw on HTTP 400 - so both rejection modes vanished without a single log line.

The Telegram branch now escapes all dynamic fields and truncates the escaped error text (with a guard against splitting an HTML entity at the cut point), matching the 800-character convention of the other channels, and sendTelegramNotification logs the status and response body when Telegram rejects a message.

Tests: test/notifications/telegram-text.test.ts covers escaping (including & ordering), truncation, entity-safe cuts, and that a pathological all-markup error still produces a sub-4096 payload. 6/6 green locally (vitest, test/vitest.config.ts).

Built by breken, your AI support engineer - breken.ai - this one's on us.

RetriggerView in GreptileConfidence Score: 4/5

The PR should not merge until Telegram API rejections propagate to callers so failed connection tests cannot report success.

Findings

  1. P1 Rejections Still Report Success ▶
  2. P2 Metadata Can Exceed Limit ▶
  3. P2 Truncation Can Corrupt Unicode ▶

Summary

  • Escapes project, application, type, and error fields before HTML interpolation.
  • Truncates escaped build errors while avoiding partial HTML entities.
  • Captures non-success Telegram response status and body.
  • Adds focused tests for escaping and truncation behavior.

Comment on lines +132 to +134
console.error(
`Telegram notification failed with status ${response.status}: ${await response.text()}`,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Rejections Still Report Success

Telegram API rejections are logged, but sendTelegramNotification still resolves after a non-success response. As a result, testTelegramConnection returns true and the settings UI displays “Connection Success” even when Telegram rejects an invalid token, chat ID, or message. The sender must propagate the rejection to its caller instead of swallowing it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 9daee3e: sendTelegramNotification now returns a real delivery boolean (false on non-2xx and on thrown errors), and testTelegramConnection throws when Telegram rejects the test message instead of reporting a false success. Covered by new red/green tests (rejection -> false, 2xx -> true).

await sendTelegramNotification(
telegram,
`<b>⚠️ Build Failed</b>\n\n<b>Project:</b> ${projectName}\n<b>Application:</b> ${applicationName}\n<b>Type:</b> ${applicationType}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}\n\n<b>Error:</b>\n<pre>${errorMessage}</pre>`,
`<b>⚠️ Build Failed</b>\n\n<b>Project:</b> ${escapeTelegramHtml(projectName)}\n<b>Application:</b> ${escapeTelegramHtml(applicationName)}\n<b>Type:</b> ${escapeTelegramHtml(applicationType)}\n<b>Date:</b> ${format(date, "PP")}\n<b>Time:</b> ${format(date, "pp")}\n\n<b>Error:</b>\n<pre>${truncateTelegramText(escapeTelegramHtml(errorMessage))}</pre>`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Metadata Can Exceed Limit

Only the error text is length-limited; the escaped project and application names remain unbounded. These names have no server-side maximum, and escaping can expand each character up to fivefold. A sufficiently long or markup-heavy name can therefore push the complete message beyond Telegram’s 4096-character limit, causing the build-failure notification to be rejected. Bound the assembled payload or reserve limited space for every dynamic field.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 9daee3e: escaped project, application, and type names are now each wrapped in truncateTelegramText(..., 200), so the composed message can never approach Telegram's 4096-char ceiling.

return escapedText;
}

let truncated = escapedText.substring(0, maxLength);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Truncation Can Corrupt Unicode

substring(0, maxLength) truncates UTF-16 code units and can split a non-BMP character when an emoji or similar build-output character crosses the cutoff. This leaves a lone surrogate that is replaced during UTF-8 encoding, corrupting the final character before the ellipsis. Truncate by Unicode code points or back off when the cut falls inside a surrogate pair.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 9daee3e: truncateTelegramText now backs off one more code unit when the cut leaves a dangling high surrogate, so non-BMP characters (emoji in build output) cannot corrupt the payload. Regression test added - it is red against the pre-patch truncation.

- sendTelegramNotification now returns a real delivery boolean;
  testTelegramConnection throws on rejection instead of reporting a
  false success
- wrap escaped project/application/type names in
  truncateTelegramText(..., 200) so a message can never approach
  Telegram's 4096-char ceiling (which Telegram answers with a silent
  400)
- truncateTelegramText backs off a dangling high surrogate at the cut
  so emoji and other non-BMP characters in build output cannot corrupt
  the JSON payload

Tests: 9/9 green; the surrogate and delivery-result cases are red
against the pre-patch source (surrogate cut adjusted to actually
straddle the pair).
@Siumauricio Siumauricio closed this Sep 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Telegram build-error notifications are silently dropped: errorMessage is neither truncated nor HTML-escaped, and the API response is never checked

3 participants