Reading the execution node by node
The execution list only said success. Opening the run and stepping through it told the real story. The Groq node had returned a full script with an episode title, a description and a list of segments. The node after it, which parses that text, output a generic default title, no description text and an empty segments array. The Split Segments node after that output no items, and nothing later on the graph executed at all.
It wasn't truncation
The first instinct with broken LLM JSON is that the output was cut off, and the fix is a higher token limit. Here the response finished with finish_reason: stop and used about 1,200 of the 3,000 tokens allowed. The model finished its answer; it just wrote a syntax error partway through. Feeding the same text to a strict parser failed with a delimiter error near character 3,690. Raising the limit would have changed nothing.
Two behaviours that add up to a silent success
- The catch block. On any parse failure it replaced the result with a default title and
segments: [], a structure that looks valid and is empty. A different flavour of the same pattern is in the control-character article, where the fallback text was published live. - Zero items. When a node emits an empty list, n8n runs nothing after it and marks the execution successful. That is correct for a workflow that legitimately has nothing to do, and indistinguishable from a bug.
What I'd change
- Let the catch block fail the run: throw an error, or return an explicit failure flag that routes to a Telegram alert, instead of fabricating a plausible empty result.
- Add a guard right after parsing: if the segments list is empty, throw. A daily episode with no segments is never a valid outcome.
- Retry the LLM call once before giving up. A second sample usually returns valid JSON.
- Treat run duration as a signal. A run that normally takes around half an hour and finishes in 12 seconds is worth an alert on its own.
The publisher workflow two hours later found no ready episode and exited with a "no ready episode" message, which is also a success. Two workflows behaved correctly around a hole neither of them could see. For other ways a run can report success while doing nothing right, see four ways an n8n workflow can report success.
A parse node that fails loudly
This is the shape of the parse step I now use for LLM JSON in a workflow where an empty result is never valid. It throws on bad JSON and on a structurally valid but empty result, so the execution turns red and the error workflow fires:
// Code node "Parse Script" (Run Once for All Items)
const raw = $input.first().json.choices?.[0]?.message?.content ?? '';
let data;
try {
data = JSON.parse(raw.replace(/^```(json)?|```$/g, '').trim());
} catch (e) {
throw new Error(`LLM returned invalid JSON (${raw.length} chars): ${e.message}`);
}
if (!Array.isArray(data.segments) || data.segments.length === 0) {
throw new Error('LLM JSON parsed but contains no segments');
}
return [{ json: data }];
If you want a retry before giving up, put the retry on the LLM node (Settings, Retry On Fail) and connect the parse node's error output to a second LLM call, rather than catching the error and returning a default.
Detecting runs that are suspiciously short
Duration turned out to be the clearest symptom. A few ways to use it:
- Last-node check. End the workflow with a node that only runs when an episode was really queued, for example a Telegram message with the episode id. If that message doesn't arrive, something went wrong, whatever the execution status says.
- Daily audit. A separate scheduled workflow reads the render queue and alerts when there is no row for today's date. It doesn't care why the row is missing.
- Execution timing. The n8n API returns
startedAtandstoppedAtfor each execution. A small workflow can compare the last run's duration with the usual range and alert on outliers.
The queue audit is the one I'd add first. It catches this bug, a skipped schedule and a Creator that crashed before its first node, with one check.
Why "zero items" is not an error in n8n
n8n treats an empty item list as "nothing to do". Every node after it is skipped, and the execution ends successfully. That is the correct behaviour for many workflows: a poller that finds no new emails, or a filter that removes everything, should not page anyone. It only becomes a problem when the empty list was produced by a fallback that hid a failure. The fix is not to change how n8n treats zero items, but to make sure that zero items only ever means "legitimately nothing to do".
Frequently asked questions
Why did the n8n execution show success when nothing was produced?
The parse node's catch block returned an empty segments list instead of failing. The next node emitted zero items, n8n skipped everything after it, and an execution with no errors counts as a success.
Was the LLM response cut off by the token limit?
No. The response ended with finish_reason stop and used about 1,200 of 3,000 tokens. The model wrote complete but syntactically invalid JSON, so raising the limit would not have helped.
Should a catch block in a Code node return default values?
Only when a default is a valid outcome. If an empty result is never acceptable, throw an error so the run fails visibly and the error workflow can alert you.
What is the simplest guard against silent empty runs?
A separate check that alerts when the expected output, such as today's queue row, does not exist. It catches this and several other failure modes at once.
n8n Automation Hub