Custom emoji fail silently for an ineligible bot — verify by round-trip - #3
Open
hlibsuslov wants to merge 4 commits into
Open
Custom emoji fail silently for an ineligible bot — verify by round-trip#3hlibsuslov wants to merge 4 commits into
hlibsuslov wants to merge 4 commits into
Conversation
…und-trip The skill told agents to expect a rejection when a bot may not use custom emoji, and to handle it as a validation error. That is wrong, and it makes every fallback ladder built on it dead code. Verified against the live Bot API on 2026-08-05, with a bot whose owner has an active Premium subscription sending to that owner's private chat — squarely inside eligibility route 2 as documented. Telegram returned ok: true and stripped the entities. Reproduced identically through parse_mode=HTML <tg-emoji>, explicit MessageEntity with a correct UTF-16 offset, and sendRichMessage with RichTextCustomEmoji in a table cell: entities came back empty and the cell came back as a plain string. No exception is ever raised. So the reference now teaches the only check that works — read the sent Message back and look for the entity — and records what does not work: icon_custom_emoji_id is echoed verbatim regardless, so a button field proves nothing. Also documented, from the official docs and the same test session: the MTProto rule that an entity must wrap exactly one emoji equal to the sticker's alt or the server ignores it; the supergroup custom_emoji_sticker_set_name route; that polls accept only custom emoji entities; that sendSticker refuses emoji stickers outright; and the image route that does work — a sticker's thumbnail or file downloads and re-uploads as an ordinary photo. That matters because fallback characters collide: a real 80-emoji pack measured here had only 52 distinct ones, so five different emoji all showed as the same character.
The section claimed the failing bot's owner had active Premium, which would have made it a Telegram-side gap. It did not: the owner's subscription was inactive, so this is the documented ineligible case — the finding that matters is the failure *mode*, not a contradiction of the docs. Also records the mistake that produced the wrong claim, because it is an easy one to repeat: Premium-only profile fields such as emoji_status_custom_emoji_id and background_custom_emoji_id outlive the subscription, so they cannot be used to infer that a bot is eligible. Only the round-trip can.
There was a problem hiding this comment.
Pull request overview
This PR updates the project’s operational guidance around Telegram custom emoji to reflect a verified “silent strip” behavior (Telegram returns ok: true but removes custom emoji entities when the bot is effectively ineligible), and adjusts the error/fallback playbooks accordingly so agents don’t build dead fallback ladders based on expected 400s.
Changes:
- Adds a non-negotiable SKILL rule requiring proof (via round-trip inspection) that custom emoji entities actually shipped.
- Corrects the “custom emoji failure” classification in the errors/fallbacks reference by introducing a “no error at all” failure mode and re-scoping the 400 case to malformed payload/IDs.
- Expands the custom emoji reference with a verified silent-strip section, round-trip verification approach, and practical fallback guidance (including the “show a picture instead” route).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| SKILL.md | Adds a hard rule to verify custom emoji delivery by inspecting the returned Message, not by error handling. |
| references/errors-and-fallbacks.md | Updates the failure-mode table to include silent stripping and corrects the 400-row guidance. |
| references/custom-emoji.md | Documents the silent-strip behavior, round-trip detection, and fallback strategies across surfaces. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…lly blocks Agents were reaching for markup fixes and retries when custom emoji did not appear, because nothing in the skill said loudly enough that the capability is gated on the *owner's* Telegram Premium. Combined with the silent strip, that is a debugging trap: there is no error to follow, so the search goes to the one place the problem never is. custom-emoji.md now opens with the rule and the instruction to stop: check whether the account that created the bot has an active Premium subscription (or a Fragment username), tell the user if it does not, ship the Unicode fallback, and confirm the fix by round-trip. An expired subscription behaves exactly like never having had one, so 'they had Premium once' is not an answer. SKILL.md carries the same as a non-negotiable rule and in the button section.
The section rested on a single negative result, which cannot distinguish 'not entitled' from 'broken somewhere else'. The same bot was re-tested after Premium was activated on the owner account, with no code change: the entity survived through all three paths, including inside a rich table cell. Both halves of the experiment are now stated.
hlibsuslov
force-pushed
the
docs/custom-emoji-verified
branch
from
August 7, 2026 14:06
92ce6f6 to
cb4c839
Compare
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.
The skill told agents to expect a rejection when a bot may not use custom emoji, and to handle it as a validation error. That is wrong — and it makes every fallback ladder built on it dead code.
Verified against the live Bot API on 2026-08-05, on a bot that qualified for neither route (no Fragment username, owner Premium not active). Telegram returned
ok: trueand stripped the entities. Reproduced identically through three paths:parse_mode=HTML+<tg-emoji>entities: []MessageEntity, correct UTF-16 offsetentities: []sendRichMessage+RichTextCustomEmojiin a table cellNo exception is ever raised, so the failure is invisible to any error-driven fallback.
What changed
references/custom-emoji.md— the silent-strip failure mode; the read-the-message-back check that is the only reliable probe; whyicon_custom_emoji_idcannot be used to detect eligibility (Telegram echoesreply_markupverbatim); and a caution that Premium-only profile fields outlive the subscription and must not be used to infer eligibility.alt, or the server ignores it — same silent failure mode.sendStickerrefuses emoji stickers (can't send emoji stickers in messages), but a sticker's thumbnail or file downloads and re-uploads as an ordinary photo. This matters because fallback characters collide — a real 80-emoji pack measured here had only 52 distinct ones, so five different emoji all showed as the same character.custom_emoji_sticker_set_name, polls accepting only custom emoji entities, reactions treating bots as non-premium.references/errors-and-fallbacks.md— corrected the "expect a 400" row, added the no-error-at-all failure class.SKILL.md— one non-negotiable rule: prove custom emoji shipped.Local validators pass.