A threshold of zero read as no threshold: how to spot it
You set a threshold on an ad account, the tool runs on schedule, and you assume you are covered. There is one value that breaks that assumption without telling you: zero. In a great many systems, a threshold of zero is read as "this person has not set a threshold", and a default number is used instead.
The damage runs in two directions. If your threshold was a spend cap, nothing gets capped and the money keeps going out. If your threshold was a rule for pausing weak campaigns, the tool applies a default that is nothing like the number you chose, and pauses campaigns that were doing fine. Both times the screen still shows your number, the daily run still completes, and no error appears anywhere.
This article shows you how to find that behaviour in whatever tool you use, with a ten-minute test that needs no technical knowledge. The case that prompted it is described in full first, because the shape of the fault is easier to recognise once you have seen one.
What went wrong
The setting was an automation module for ad accounts. You build a rule out of conditions and actions, it runs on a schedule, and each condition carries a threshold you type in.
One condition is "return on ad spend below threshold". You enter a number, the system compares it against each campaign, and campaigns below it are selected.
The code that read that number said, in effect: take the user's value; if it is empty, use the default. This is a line most programmers write without thinking, it reads well, and nine times out of ten it is correct.
The tenth time is the problem. In most languages the emptiness test returns true for more than an unfilled field. It is true for an empty string, for an empty list, for an undefined value — and for zero.
So a user typed zero into the threshold, and the code understood "nothing entered" and quietly substituted the default. Fourteen campaigns matched a condition that, as the user had written it, should have matched almost nothing.
Why somebody types zero on purpose
This matters, because it is what separates a real fault from a theoretical one.
The zero was not a slip. The intention was precise: flag every campaign that produced no revenue at all.
For anyone running ads that is an ordinary request, and a good one. Return on ad spend of zero means money went out and nothing came back, which is exactly the thing you want to hear about first, before anything subtler.
The system read that intention as its opposite — as an absence of instruction — and carried on with a number nobody had chosen.
The advertising fields where zero is an instruction
Zero carries a real meaning in more places than most people expect. Every field in this table is a candidate for the same misreading, and every one of them is worth ten seconds of thought when you set it.
| Field | What zero means when you type it | What goes wrong if it is read as unset |
|---|---|---|
| Return on ad spend threshold | flag campaigns that produced no revenue | a default is used and healthy campaigns get selected |
| Conversions threshold | flag campaigns with no conversions at all | the campaigns you most needed to see are skipped |
| Spend cap | stop spending on this | nothing stops, and the money keeps going out |
| Bid adjustment | no adjustment, bid the base amount | an old adjustment stays in force silently |
| Frequency cap | a deliberate limit of none | the platform default applies instead of yours |
| Minimum clicks before acting | act immediately, no minimum | a default minimum blocks the rule from ever firing |
The last row is the nastiest, because the failure is silence. A rule that never fires produces no output to inspect, no wrong recommendation to question, and no reason for anyone to look at it again. It just sits there, in the list of rules you believe are protecting you.
Go through your own rules once with this question in hand: for each numeric field, is zero a value I might genuinely want? Where the answer is yes, that field goes on the list to test.
Why nothing warned anyone
Three properties keep this kind of fault alive.
There is no error. The system executes its logic correctly. No exception is raised, nothing lands in the error log, no warning reaches the user. Every monitoring tool reports a healthy system, because by its own standards the system is healthy.
The output stays believable. The fourteen flagged campaigns were real campaigns with real figures, and the reasoning attached to them was correct relative to the threshold the system was using. Nobody reading the recommendation had any way to tell that the threshold was not theirs.
It happens at exactly one input value. Enter 1, enter 2, enter 0.5 — all correct. Only zero fails. Which means every ordinary test passes, because nobody thinks to test with zero on purpose.
That third property is the one that decides how you find it, and it rules out most testing habits.
Where the zero disappears
A number you type travels through four layers before it decides anything, and it can be lost at any of them.
The input box. Does the screen send the number 0, or does it send an empty string because the field was cleared and retyped?
Storage. Can the record hold 0 as a value that is different from no value at all? Some schemas cannot, and the distinction is lost before any logic runs.
Reading it back. Where the default substitution lives, and where the case in this article broke.
The comparison. "Greater than zero" and "greater than or equal to zero" behave identically for every value except one.
With a tool you bought, you cannot inspect the first three. You can inspect what comes out of the fourth, and that turns out to be enough.
The ten-minute test
Set a condition that nothing could possibly satisfy, run it, and check that nothing happens.
That is the whole test. It is simple, it is rarely done, and it catches this class of fault when nothing else will. The reason it is rarely done is instinct: people test that the thing they want works, not that the thing they do not want stays quiet.
Run it in two halves.
Case A — a threshold guaranteed to match. Set it where, on current data, nearly every campaign qualifies. You expect the rule to fire. If it does not, the rule is broken outright, which is the easy failure to find.
Case B — a threshold impossible to match. Set it where no campaign could qualify. You expect silence. If anything fires, the rule is matching for a reason other than the one you wrote.
The zero surfaced in case B. Setting the return-on-ad-spend threshold to zero, a value nothing can fall below, still returned fourteen campaigns. The moment you see that, you know the system is not using the zero.
Applied across every condition and every action in that module — seven conditions and ten actions, two cases each — the exercise came to thirty real runs and turned up three separate faults. It took about a working day. The zero was the first of the three.
Eight checks for your own account
None of this needs you to build anything. The following eight take under an hour in total and are worth repeating once a quarter.
Write down every threshold you set. Four columns: rule name, threshold, unit, date set. It sounds too simple to bother with. Three months from now you will not remember the number you chose, and if you cannot remember it you cannot notice that the system is using a different one.
Reconcile once a month. Open the settings, compare the stored thresholds against your notes. Five minutes. This catches system faults and human ones equally — a colleague who edited a rule and did not mention it looks exactly the same from the outside.
Find the fields where zero means something. Go through your rules and ask, for each numeric field: if I typed 0 here, what would I be asking for? Any field where that question has a real answer is a field to test.
Run the impossible test on those fields. Set a threshold nothing can meet, run, confirm silence, restore. Ten minutes.
Read the reasoning, not just the verdict. When a tool recommends pausing a campaign, the explanation should quote the threshold you set. If it cites a number you do not recognise, you have found something.
Ask for the per-run limit. How many campaigns can one run touch? The answer has to be a number. "It's safe" is not a number, and a system with no limit is relying entirely on its logic being correct.
Check the log of runs that did nothing. "Ran, nothing matched" and "did not run" must look different in the record. If they look the same, you cannot tell a healthy quiet system from a dead one.
Keep one alert that must always fire. A rule certain to trigger — something trivially true about your account. If it ever goes quiet, the path is dead, and you learn that from the alert that stopped rather than from the alert that never came.
What the fix looks like
The repair was to replace the emptiness test with a more precise question: was this value actually provided, rather than is this value falsy.
In practice that means a single small function for reading numbers that distinguishes "not provided" from "provided as zero", used everywhere a threshold is read. Centralising it is the part that matters. A fix applied in fourteen places will be forgotten in the fifteenth, written next month by someone who does not know the history.
Twenty minutes to repair. A thirty-run sweep to find. That ratio is characteristic of the whole class, and it is the argument for a procedure rather than for vigilance.
A second change came out of it, and it is the one worth copying if you build your own tooling: when a system decides a number on your behalf, it has to say so. Had the recommendation read "using the default threshold, because none was set", the user would have caught it instantly, since they remembered setting one. The silence is what hid the fault, not the substitution.
Three relatives of the same bug
The zero belongs to a family: places where a language collapses several meanings into one representation. If you write your own scripts, these three will find you eventually.
Empty string versus never provided
Someone leaves a campaign-name filter blank; someone else types a word and deletes it. Two different intentions, usually stored identically. In a "campaign name contains…" filter, does blank mean no filtering at all, or filtering for names that are empty? Two readings, two very different result sets, and no way to tell which one you got.
Empty list versus a failed fetch
The system asks whether any campaigns match and receives an empty list. Empty because genuinely nothing matched, or empty because the data call failed and returned a default?
This one is more dangerous than the zero, because it produces the conclusion "everything is fine" when the truth is "we know nothing". The fix is to treat a failed fetch as an error rather than as data, which means the fetch has to report whether it succeeded, separately from what it returned.
Negative numbers versus a sentinel
Some metrics are legitimately negative — period-over-period change is the obvious one. Plenty of systems also use a negative value as a marker meaning "no data". Where both conventions meet, a campaign whose cost fell fifteen percent reads as a campaign with no data, or the reverse.
One principle covers all three: never use a valid value to mean "no value". Obvious written down, violated constantly, because the alternative is slightly more code.
The second fault from the same sweep
Worth telling because the lesson is the same shape.
The data store held accounts billed in US dollars alongside accounts billed in Vietnamese dong. A condition with a threshold of "total spend above 500" is therefore ambiguous by a factor of roughly twenty-five thousand. The interface note at the time named a currency. The data did not.
Automatic conversion at the live rate was considered and rejected, for two reasons. Rates move daily, so the same threshold behaves differently from one day to the next and results stop being reproducible. And when something goes wrong months later, reconstructing what the rate was on a particular afternoon is genuinely painful.
The approach taken instead: require a currency on every money threshold, filter campaigns to that currency, and refuse to run when the scope mixes currencies. Refusing is more annoying than guessing. With other people's money, annoying beats wrong.
Why automation makes a silent failure worse
Misread a threshold while working by hand and the consequence is a few decisions in one afternoon. You are looking at the screen. An odd number catches your eye. You stop and ask someone.
Automated, the same misunderstanding applies to every campaign, every day, without a pause. Automation does not add judgement. It adds consistency, and that includes consistency of error.
This is the practical argument for keeping new rules on notification only until you have tested them. A rule that emails you with faulty logic produces a few strange emails and somebody notices. The same rule set to pause campaigns does the wrong thing silently and at scale, and you find out from the spend report.
The permission you grant a rule should track how thoroughly its logic has been tested, not how much you trust the technology in general. Those sound similar and are not: one rests on something you did, the other on how you feel.
Five silent failures worth recognising
The zero is one member of a broader category. Naming the category is what lets you go looking for the rest.
Misread input. A value arrives and is understood differently from how it was meant. The zero. Also dates read in the wrong format, percentages read as fractions, times read in the wrong timezone. You catch it by comparing what the system says it used against what you entered.
Stale data presented as current. A refresh failed quietly, so the tool is acting on last week's numbers. Everything looks normal because everything is normal, only old. You catch it by insisting every figure carries a fetch time that is visible somewhere.
Partial results treated as complete. A query returns the first thousand rows because of a limit nobody documented, and the system reasons about them as though they were everything. Common in reporting. You catch it by comparing one total from the tool against the same total from the source.
Silent exclusion. Records get dropped somewhere in the pipeline — malformed, unmatched, out of range — and nothing reports the count. You draw a conclusion from ninety percent of the data believing it is all of it. You catch it by requiring every filtering step to say how many items it removed.
Correct logic, wrong scope. The calculation is right and applied to the wrong set: last month instead of this month, one campaign type instead of all, active items only. You catch it by requiring the scope to be printed next to the result rather than assumed.
What unites the five is that no error occurs and the output stays plausible. Ordinary testing — does it run, does the number look sensible — passes every time. The countermeasure is the same in each case: make the system state its assumptions alongside its answers. Which threshold it used, when the data was fetched, how many rows it read, how many it dropped, what scope it covered. None of that makes the logic correct. All of it makes wrong logic visible, and visible is the whole game.
What anomaly detection has to get right
The episode clarified something about alerting tools in general, and it is less about detection than people expect.
It has to separate unusual from wrong. A campaign spending three times its normal amount is unusual. If you launched a sale yesterday, it is also correct. A system that flags deviation with no notion of intent generates alerts nobody can act on, and within a month nobody reads them. The fix is not better statistics, it is a way to record that something is expected: a scheduled promotion, a seasonal shift, a deliberate budget increase.
It has to separate a data problem from a performance problem. Conversions dropping to zero overnight is either a catastrophe or a broken tracking tag, and those need opposite responses. A system that cannot tell them apart will confidently recommend pausing campaigns that are performing fine and reporting badly. A useful heuristic: when a metric moves discontinuously — not sharply, but to exactly zero or to an impossible value — suspect measurement before suspecting reality. Real performance rarely moves in perfect steps.
It has to age its own alerts. Something flagged today and still flagged in three weeks is not an anomaly any more, it is the new normal, and alerting on it trains people to ignore the channel. There has to be a way to acknowledge an alert or reset the baseline.
It has to be quiet by default. The best predictor of whether an alerting system survives its first quarter is how often it says nothing. Systems that produce daily output get muted. Systems that speak twice a month get read.
None of those four are detection problems. They are judgement and interface problems, which is why anomaly detection disappoints when it is treated as a statistics exercise.
What to ask a vendor
If you are choosing a tool rather than building one, these questions get further than a feature comparison, and a good vendor will enjoy answering them.
"What happens if I set this threshold to zero?" Direct, and the reaction tells you a lot. A confident immediate answer means somebody has thought about it. Vagueness means nobody has.
"Show me an output where the system decided not to act, and explain why." This tests whether inaction is recorded at all. A tool with no record of deciding not to act cannot tell you the difference between working correctly and not running.
"What is the maximum number of things one run can change?" If there is no answer, there is no limit on how far a single logic error can spread.
"Can I see the reasoning behind a recommendation, not just the recommendation?" If reasoning is not surfaced, you cannot verify anything the tool tells you, and every other guarantee rests on trust.
"When was the last bug you found in your own automation, and how did you find it?" The most revealing of the five. Everyone has bugs. A vendor who can describe a specific one and the method that surfaced it is telling you they look. A vendor who cannot recall any is telling you something too.
None of these require technical knowledge to ask or to judge. They are questions about process, and the answers separate tools built by people who have operated them from tools built by people who have only demonstrated them.
Three questions about any system that calls itself safe
Does it know when it does not know?
The zero was dangerous because the system was confident. There was no state meaning "I am unsure what this value represents" — it took a default and continued. A well-built system distinguishes three states: it knows, it does not know, and it suspects the data is wrong. Collapsing all three into "proceed" is where a lot of silent failures start. The quick check: does the tool ever answer "not enough data to conclude"? A system with an answer for every question is showing you a warning sign.
If one thing is wrong, how far does it spread?
With the zero, the reach should have been every matching campaign. A per-run limit on how many campaigns one run may touch held it to a handful. That limit does not correct the error; it bounds it. Systems that move money should be designed from this question rather than from "how do we avoid being wrong", because never being wrong is unattainable and being wrong without it spreading is not.
Can anyone reconstruct what happened?
This fault was found because a person sat down and compared results against intent. A system running in the dark — no log, no reasoning, nobody reading — would have kept running wrong indefinitely. Logging does not make a system correct. It makes incorrectness visible, and visibility is the precondition for fixing anything.
Frequently asked questions
Did this affect live accounts?
No. It was found during internal checking, while platform commands were still running in simulation. No real change was made to anyone's account.
How do I check whether my tool has the same problem?
Set a threshold in the impossible direction — for example requiring cost 500% above target — and run it. The correct result is no action at all. If something still fires, ask the vendor why before you change anything else.
Why can this not be caught automatically?
From the system's point of view nothing is abnormal. No exception, no error, and output entirely consistent with the logic it executed. Only someone who knows what the number was supposed to mean can see the mismatch.
Does the system still use defaults?
Yes, for genuinely empty fields. It distinguishes them from zero, and when a default is applied it says so in the reasoning attached to the recommendation.
Does this mean automated tools cannot be trusted?
It means they should be trusted in proportion to how they have been checked, which is a more useful standard than trusting them because they are sophisticated.
How long does the full sweep take?
Thirty runs, each one setting a threshold, running, reading the result and recording it — about a working day for one module. Measured against the cost of a fault reaching live accounts, that is cheap.
If I write my own scripts, what should I watch for?
Three things, in order. Distinguish "not provided" from "provided as zero" from the start, because retrofitting it means auditing every parameter read in the codebase. Cap how many entities a run may touch, even while the script only reads, because eventually it will write. And log the runs where nothing happened, not only the ones with actions: an empty log means the script died, not that everything was fine, and you cannot tell those apart without the quiet entries.
Are there faults you have not found?
Almost certainly. Any system of reasonable size has defects nobody has met yet. What can be described is the method: check every condition and every action, include the impossible case, and keep a second limit in place so that an unknown fault cannot do large damage.
Do the ten-minute test this week
One line of shorthand, one zero, fourteen campaigns matched that should not have been. No error message, nothing in any log, and the output read perfectly well.
The shape is what to remember: the system does the wrong thing while every indicator reads normal. Monitoring cannot catch that, because monitoring watches for signs of trouble and there are none.
So pick one rule in whatever tool you use. Set a condition that cannot be met. Run it, and check that the system stays as quiet as it should. You will either get some reassurance for ten minutes of work, or a very good question to put to whoever built it.
Related reading: detecting anomalies in ad accounts, spend guardrails and caps, and why an ads agent has to explain itself.
Orova Ads optimises campaigns for you
Connect Google, Meta and TikTok in one place. AI reads the numbers, proposes changes and executes under the rules you set.
Explore Orova Ads