Skip to content

Slack text cuts land on UTF-16 indexes at four sites: a surrogate pair astride a limit yields an invalid payload — the invalid_attachments outcome through a different door #3622

Closed

Description

@erikdarlingdata

Summary

WebhookAlertService now cuts Slack text at four fixed character positions — the #3493 prose fragment cut and hard split, and #3618's SlackFieldText (2,000) and SlackHeading — all by UTF-16 index. A cut that lands between the two halves of a surrogate pair (any emoji or supplementary-plane character in a query text, database name, or advice string) produces an unpaired surrogate, which JsonSerializer escapes as a lone \uD83D-style unit and Slack rejects the whole payload as invalid — the same invalid_attachments outcome #3493 and #3618 were built to end, arriving through a different door, exactly once, on whichever page happens to carry an emoji at the boundary.

Fix

One helper — trim to the nearest char boundary (char.IsHighSurrogate(text[cut - 1]) → back off one) — applied at all four sites, with a pin that places a surrogate pair astride each limit and asserts the payload round-trips as valid UTF-16 and the omission count is stated in characters the reader would count. Also worth deciding: cut on the grapheme (StringInfo) rather than the code point, so a combining sequence isn't split either; cheap, and it makes the omission counts honest for the text the reader sees.

Found by #3618's review (non-blocking note; two of the four sites predate that PR, so it was left for a follow-up rather than folded). Related: #3493, #3612.

Activity

  1. erikdarlingdata commented on Sep 18, 2026

    @erikdarlingdata
    OwnerAuthor

    Closed by the merge of #3625 (its body names this issue with a closing keyword; keywords do not fire against dev, so the watcher closes it).

  2. erikdarlingdata commented on Sep 18, 2026

    @erikdarlingdata
    OwnerAuthor

    Record correction, post-merge: this issue's severity claim was wrong. System.Text.Json does not emit a lone surrogate escape — with the builder's own options it substitutes \uFFFD, the payload is valid, and Slack delivers it (measured on a net10.0 probe by the #3625 lane). So the defect was never a lost page; it was a page that lied — a � the value never held, a hard split showing two of them and no emoji, an omission count in UTF-16 units. #3625 fixed the lie (grapheme-boundary cuts at all six sites, omitted counts in characters). The invalid_attachments framing in the title should be read as refuted.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions