n8n logon8n Automation Hub
Incident report

A Raw Newline in Groq's JSON Response Made My Catch Block Post Garbage to Three Live Platforms

Incident report · n8n workflow · July 29, 2026

A daily portfolio-publisher workflow is supposed to post distinct, LLM-written copy to several platforms at once. One evening's run posted a generic one-line fallback everywhere instead — with duplicated hashtags stitched onto the end — and the cause was sitting inside the LLM's own JSON response, not in the prompt or the platforms it posted to.

Workflow at a glance
  1. A daily publisher workflow has Groq (llama-3.3-70b) write per-platform post copy as a single JSON object, then a Code node parses that JSON and routes each field to its own platform (LinkedIn, Facebook, Instagram, Telegram, and more)
  2. Groq's response that evening contained a literal, unescaped newline character inside one of the JSON string values — technically invalid JSON
  3. JSON.parse threw Bad control character in string literal, silently tripping the Code node's own catch block
  4. The catch block's fallback text — a generic title: description string, never meant to be seen publicly — went out live to LinkedIn, Facebook (post and story), and Instagram instead of the real generated copy
  5. The same fallback string also had a hashtag suffix concatenated directly onto it, then a separate hashtag safety-net function added a second, duplicate line of hashtags underneath — the visible "hashtags mixed into the text" symptom that got noticed first
  6. Fix: sanitize control characters out of the raw response before parsing, and rebuild hashtag handling so it can never add a second block regardless of which code path produced the text

The symptom that got noticed first wasn't the real bug

What actually got flagged was cosmetic: a live post had hashtags awkwardly mixed into the middle of a sentence, plus a second batch of hashtags at the end. Chasing that literally — as a hashtag-placement bug — would have missed the real story. The post itself, on every affected platform, wasn't the intended per-platform copy at all. It was a generic fallback string, and the hashtag mess was just a visible side effect of that fallback firing.

Where the fallback text actually came from

The workflow's design has an LLM generate a single JSON object holding differently-worded copy for each destination platform, which a Code node then parses and distributes. That Code node has a catch block for exactly the case where the LLM's response can't be parsed as JSON — a reasonable safety net on paper, meant to produce something rather than nothing. Its actual fallback was a bare project.title + ': ' + project.description, truncated, with a hashtag string concatenated directly onto the end. That fallback fired on this run, for every platform at once, because the parse step it was guarding against had genuinely failed.

Why the JSON failed to parse

The specific failure was Bad control character in string literal — the standard error JSON.parse throws when a string value contains a raw, unescaped control character, most commonly a literal newline where the JSON spec requires the escaped two-character sequence \n instead. Groq's response that evening had exactly that: an actual newline byte sitting inside one of the string fields, rather than its properly escaped form. Perfectly valid as a plain-text sentence, invalid as JSON.

Two separate mechanisms stacking a second hashtag block

The visible symptom had two contributing pieces, not one. First, the fallback's own hashtag suffix was glued directly onto the sentence with no line break, which alone would read oddly. Second, a separate safety-net function elsewhere in the same Code node checked only how many hashtags a piece of text already contained — if the count was below a threshold, it appended its own hashtag line at the end, with no awareness of whether hashtags already existed inline, earlier in the same text. Both mechanisms were individually reasonable; combined, they guaranteed a duplicate hashtag block on exactly the kind of text the fallback was producing.

The fix, in two parts

The immediate parse failure was fixed at the source: sanitizing raw control characters out of the LLM's response text — escaping stray literal newlines, carriage returns, and tabs to their proper JSON string-escape form — before handing it to JSON.parse, so this specific class of otherwise-valid LLM output stops tripping the catch block at all. The hashtag duplication was fixed independently, by replacing the count-based safety net with logic that strips every hashtag out of a body of text first, deduplicates them, and rebuilds exactly one trailing hashtag line — a fix that holds regardless of whether the text came from a clean parse or from the fallback path, closing off the duplication risk structurally rather than patching one specific trigger for it.

What made this worth writing up

Neither the prompt nor the platforms were the problem — an LLM producing a stray, unescaped control character inside otherwise coherent output is common enough that any pipeline parsing structured LLM output as JSON should assume it will eventually happen, rather than treating a parse failure as a rare edge case. The second lesson is just as generalizable: a fallback path meant to degrade gracefully is still real production output the moment it fires, and deserves the same scrutiny as the primary path — including checking that its own text-formatting logic (hashtags, truncation, whatever else) can't misbehave in ways the primary path never would.

An LLM step whose output silently breaks JSON.parse sometimes?

I debug production n8n + LLM workflows for a living, including the fallback paths that quietly go live instead of the actual generated content.