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.
n8n Automation Hub