Green but empty: when an automation finishes without errors and delivers nothing
At a glance. A status code of 200 says that someone answered, and nothing about what the answer contains. Every point where a result is processed further should therefore check three things: shape, quantity and age. Six cases from our operations notes between July and September 2026 show what happens without these checks.
The n8n blog uses a payment API to show how a run stays green and still calculates wrongly: one day the amount arrives as text instead of a number. The request succeeds, the status code is right, and only the processing step computes a wrong total or stops. We know the pattern from our own operations, except that it is usually a language model, a search service or an archiving job there, and not a payment API.
This post is meant to be independent of any tool. How n8n is built and what it costs when self-hosted is covered in our introduction to n8n. Here the question is what comes after: how do you notice that a green run delivered nothing?
What a silent failure is
A silent failure is a run that succeeds technically and delivers nothing in substance. The status code is 200 and the job reports success, but the field is empty, the list has zero entries or a value has a different type than expected. Classic monitoring answers whether something is running, and leaves open whether the result is usable.
We described a similar gap when monitoring our DNS resolvers: reachable does not mean healthy, in the post Why a green uptime check proves nothing. Here the gap sits one level higher, in the content of the answer.
What the n8n blog says
The n8n post of 1 October 2026 names three changes that return valid HTTP responses and still break business logic: a JSON object gets wrapped in an array, an enum value changes and the flow takes the wrong branch, a name is split into two fields. Its recipe is to validate the response right behind the call against an expected schema, report violations to people immediately, and keep every validation in the execution history. According to n8n this also includes versioned contracts and a decision whether to check every request or only a scheduled sample.
Two caveats. The post comes from the vendor and ends with an advertisement for its own product; the ideas are not tied to n8n, though, as the post itself says. And the term comes from consumer-driven contract testing: the Pact documentation explains that this approach tests only the parts of an interface that the consumer actually uses.
Six cases from our operations
Every row comes from our own operations notes. The numbers are our own measurements on our LiteLLM gateway and the systems named; the models are listed under the table. This is a summary of those notes and not a new measurement series.
| Date | System | What was green | What was missing | Value |
|---|---|---|---|---|
| 3 July 2026 | Order import from PDF | Model call ran through | After a swap from an instruct to a thinking model: the vision branch returned zero fields after 65 s, the text branch returned broken JSON | 65 s, 0 fields; after the correction 4.9 s and correct |
| 17 August 2026 | Chat title generation | Call without error | Title stayed empty, stop reason length | 256-token budget: 5 of 5 empty; 2048: 2 of 5 empty; without thinking: 0 of 5 |
| 28 August 2026 | Evaluating job applications | HTTP 200 from the gateway | Content empty, stop reason length | 400-token budget: 12 of 12 empty |
| 28 August 2026 | Check of 36 MCP servers | All 36 answered initialize and tools/list | A search service returned HTTP 200 with an empty result list | 36 of 36 green; only a real read call found the outage |
| 30 August 2026 | Email archiving | Every minute "sync finished successfully" | One mailbox archived nothing for 9.4 months | Progress pointer at 187 with 46 messages present |
| 7 September 2026 | Session titles in our agent interface | Call without error | Title stayed the first message, content null | 64-token budget, stop reason length |
Measurement carrier: the title measurement of 17 August ran against a Qwen3.5-122B-A10B model in NVFP4 format behind our gateway. The measurement of 28 August ran through the gateway on the thinking lane with real application texts instead of filler text; the note does not name the model. A re-measurement on 29 August with an 8192-token budget on six real CVs did not reproduce the failure (6 of 6 complete), but one used 7755 of 8192 tokens. Archive and MCP check are state measurements on running systems without a model.
Two rows deserve a closer look. In the archiving job the progress pointer sat above reality: the mailbox had been rebuilt at some point, message numbers started again at one, and everything new therefore fell below the stored pointer. The job was successful by its own definition; it just found nothing. Of roughly 500 delivered mails we could recover 79, the rest had been deleted before anyone noticed. After that we added a check that measures the age of the newest archived message per source, with 14 days as the threshold.
The titles failed the same way twice, in two different applications. A reasoning model spends its token budget on thinking and returns no content, without reporting an error. Our operations note of 28 August records a rule for this: when the model returns nothing, do not return an empty text, throw a dedicated exception, retry once with double the budget, and log the reason.
Where contracts change silently
In every case the interface stayed the same, and a detail of the content changed: a different model, a different token budget, a different state behind the service. Fields can also be renamed. The vLLM documentation states in its current version that the field reasoning_content is now called reasoning, and that a client could otherwise silently read an empty reasoning_content while reasoning is filled. The LiteLLM documentation, in turn, standardizes on reasoning_content. Which field arrives where in our setup therefore depends on whether a gateway sits in between. We have not measured this version by version.
Three checks that help any pipeline
Shape. Right behind the call, check that type and required fields match. This is the part the n8n post describes, and it can be built in any tool, including a plain Python script.
Quantity. Count how much result the run produced. A list with zero entries, a text with zero characters or an import with zero records is an alarm, unless someone has explicitly decided that zero is normal for this job.
Age. Measure how old the newest result is. This is the check that would have found the archive, and it needs no knowledge of why something stopped.
A sketch for the first two checks on a chat answer in the common format:
def check(answer: dict) -> list[str]:
problems = []
choice = (answer.get("choices") or [{}])[0]
text = (choice.get("message") or {}).get("content")
if not text or not text.strip():
problems.append("content empty")
if choice.get("finish_reason") == "length":
problems.append("output truncated")
return problems
The sketch is not an excerpt from our operations. It shows how small the check can be. What happens with the list of problems is decided in the next section.
Who gets the alarm
A check nobody reads does not help. Our work on the archiving job taught a second lesson: the first version of the new check hung on the condition "nothing found so far". Because another mailbox had been in a permanent error state for weeks, the new check would have been permanently silent. A permanent fault masks every check that runs after it. The dry run caught this; since then both conditions hang on a query of their own.
The archive check reports only on a change of state, so the same mail does not arrive every 15 minutes. How we probe services with Uptime Kuma and have outages reported is covered in the post on Uptime Kuma. If you would rather not build such checks for your jobs yourself, see our offer for IT operations. And anyone building language models into their workflows will find more cases, where a setup holds or does not, in the overview AI in production.
Still to measure
What remains open is how widespread the gap still is in our own workflows. We did not measure this for the post. Method: list all scheduled jobs per host (cron, timers, workflow executions), then check per job whether it has a quantity check and whether an empty output triggers an alarm. The result would be a number of the form "n of N jobs alarm on zero results". Second question, also answerable read-only: in the execution histories of the last 30 days, count how many runs are marked successful and have zero outputs.
What is established and what stays open
Established by the sources: the three change patterns and the recipe from the n8n post, the description of the consumer-driven approach at Pact, the rename of reasoning_content to reasoning at vLLM, the standardization at LiteLLM. Established by our operations notes: the six cases in the table with the numbers given there.
Open: how often this happens in our workflows today (see above), whether the field name behind our gateway differs by version, and whether the age check has a sensible threshold in every job. Sources last checked: 5 November 2026. The n8n statements come from the vendor.
Ten minutes on Monday
- Pick the job whose result flows into a mail, a booking or a decision.
- Open its last 30 runs and count how many outputs each run produced. If a run marked successful shows a zero, you have found the failure.
- Check whether a permanent fault elsewhere silences your error message.
- Set one rule: zero outputs is an alarm, and it goes to a person who actually reads it.
Further Reading
What is a silent failure in an automation?+
A run that succeeds technically and delivers nothing in substance. The status code is 200 and the job reports success, but the field is empty, the list has zero entries or a value has a different type than expected. Error handling never fires because, formally, nothing went wrong.
Is a schema check enough to find these failures?+
It finds changed fields and types, for example a number that suddenly arrives as text. An empty but formally valid answer only fails a schema if the schema demands minimum lengths or counts. So a second check belongs next to it, one that counts how much output a run actually produced.
Why does a reasoning model sometimes return an empty answer?+
Because thinking and answer can share one token budget. If it runs out before the first character of the answer, the API returns empty content with the stop reason length. In our measurements of 17 and 28 August 2026 this happened without any error message.
senn-tech