n8n logon8n Automation Hub
Debugging notes

My n8n Telegram Node Failed With "Can't Find End of the Entity"

Debugging notes · n8n workflow · September 2, 2026 · Updated October 13, 2026

Most runs posted fine. One run, out of nowhere, came back with a Telegram 400 about an unclosed formatting entity — in a message with no formatting in it, as far as anyone had intentionally written it.

Workflow at a glance
  1. A news-digest workflow builds a bilingual post from an LLM and sends it to a Telegram channel via sendMessage
  2. One scheduled run failed at that node: 400 - Bad Request: can't parse entities: can't find end of the entity starting at byte offset 1049
  3. The node's text field had no parseMode set, so Telegram's Bot API applied its own default formatting parser to whatever text arrived
  4. The LLM-generated post text happened to contain a stray * or _ character partway through — plain punctuation to a human, an unclosed bold/italic marker to Telegram's parser
  5. Two manual retries minutes later succeeded, because the next generated text for that slot didn't happen to contain the same character pattern — the underlying node was still unfixed
  6. Fix: set additionalFields.parseMode to none on the node, since the post was never meant to carry Markdown formatting in the first place
Diagram of the news-digest workflow with the Telegram sendMessage node that threw the entity-parsing error highlighted

Node diagram reconstructed from the live workflow via the n8n API.

A post like any other, until it wasn't

The workflow pulls a story from an RSS feed, runs it through Groq to produce a bilingual title and body, and posts the result to a Telegram channel. It had been running on schedule for weeks without incident. Then one run failed at the Telegram: sendMessage node with a 400 response: Bad Request: can't parse entities: can't find end of the entity starting at byte offset 1049.

Nothing about the workflow's structure had changed. The node's configuration — chatId, a text expression pulling the generated title and body, appendAttribution: false — was the same one that had sent every prior post successfully. The only thing different was the content of that one message.

An error about formatting, in a message with none written

"Entities" in Telegram's Bot API terminology means Markdown or HTML formatting spans — bold, italic, links, code — inside a message. Nobody had written any formatting into this post; it was a plain generated title followed by a plain generated paragraph. But the node's additionalFields had no parseMode specified at all, and when that field is left unset, Telegram's API does not treat the text as plain — it applies its legacy Markdown parser by default. Any character that parser recognizes as a formatting marker gets treated as one, whether or not it was meant that way.

Byte offset 1049 landed inside the LLM-generated body text. Somewhere in there sat a bare * or _ — the kind of character that shows up naturally in generated prose (emphasis, an aside, a stray asterisk from a source snippet) without anyone writing actual bold or italic syntax. Telegram's Markdown parser saw the opening marker, scanned forward for a matching close, didn't find one before the message ended, and rejected the whole request rather than guess.

Why retries looked like the problem had gone away

Two follow-up executions minutes after the failure both succeeded, at the same node, with the same configuration. That wasn't a fix — it was survivorship. Each run generates fresh LLM text for a different source story, and the next two stories' generated text simply didn't happen to contain an unmatched formatting character in that particular pass. The node's actual exposure to this failure hadn't changed at all; it would resurface on the next post whose generated text happened to include a stray marker, on a schedule with no way to predict which run that would be.

The fix: stop asking Telegram to parse formatting it was never meant to see

The post was never designed to carry Markdown — title and body, plain text, nothing bolded or linked. The correct fix wasn't to sanitize or escape every possible Markdown character out of LLM-generated text (fragile, and easy to miss a case), it was to tell Telegram not to parse the text as Markdown at all: additionalFields.parseMode set explicitly to none. With no parse mode, Telegram delivers the text exactly as sent, byte for byte, with no entity scanning and nothing left to fail on.

The change was a one-field addition to the node's existing configuration — no change to the prompt, the RSS parsing, or anything upstream that produces the text.

What confirmed it was actually fixed

The node's live configuration was re-read via the n8n API after the change to confirm parseMode: none was actually saved, rather than trusting a green save button. The class of failure this closes is specifically about text that was never supposed to be formatted in the first place; a workflow that genuinely needs bold or linked text in its Telegram messages needs the opposite fix — keeping a parse mode, but escaping the small set of characters that mode reserves before the text is ever sent.

Telegram parse modes compared

Parse modeCharacters with special meaningGood for
None (plain text)NoneGenerated text, user input, anything you don't control
HTML<, >, &Templates with a few bold words or links
Markdown (legacy)*, _, `, [Simple fixed messages; avoid for generated text
MarkdownV2Many, including . - ! ( ) # + = |Fixed templates where every variable part is escaped

HTML mode is the easiest of the formatted modes to make safe, because only three characters need escaping.

If you do want formatting: escape the variable parts

When a message needs a bold title but the body comes from an LLM, keep HTML mode and escape only the generated text, before it goes into the template:

// Code node
const esc = s => String(s ?? '')
  .replace(/&/g, '&amp;')
  .replace(/</g, '&lt;')
  .replace(/>/g, '&gt;');
return [{ json: { text: `<b>${esc($json.title)}</b>\n\n${esc($json.body)}` } }];

The order matters: & first, otherwise the ampersands of the other replacements are escaped again.

Reading "byte offset" in the error

Telegram reports the position in bytes, not characters. In text with umlauts, Cyrillic or emoji, one character takes two to four bytes, so the offset points further into the message than the character count suggests. To find the culprit, encode the message as UTF-8 and look at the bytes around that offset. In a bilingual post the problem is often in the second language, even when the offset number looks like it belongs to the first.

The other limit worth knowing in the same node: a Telegram text message can be at most 4,096 characters. Generated bilingual posts can reach that, and the error for it is different, so it is worth truncating before sending.

Frequently asked questions

What does 'can't parse entities' mean in Telegram?

Telegram tried to read the text as formatted Markdown or HTML and found a formatting marker without a matching end, such as a stray * or _.

Which parse mode should I use for LLM-generated text?

None, unless you need formatting. If you do, use HTML mode and escape <, > and & in the generated parts.

Why do retries sometimes make the error disappear?

Each run generates different text. The error only appears when the text happens to contain an unmatched marker, so successful retries don't mean it is fixed.

What is the maximum length of a Telegram message?

A text message can be at most 4,096 characters. Longer generated posts need to be shortened or split.

A Telegram node that fails on some messages but not others, with an "entity" error that doesn't point at anything you wrote?

I debug n8n Telegram integrations where LLM-generated text collides with Telegram's Markdown/HTML parsing.