Product

The difference between a bug and a missing spec

Editorial · Reveneau · October 22, 2026

The difference between a bug and a missing spec

We once watched a team spend most of a sprint on a "bug" where a discount code applied twice when a customer changed their shipping address mid-checkout. Three engineers looked at it. The code was doing exactly what the checkout flow said to do: recalculate the cart, reapply any valid code. Nobody had ever written down what should happen to a discount code when the address changes. The ticket called it a bug. It was a gap.

That mix-up costs more than a wasted sprint. Here is the rule we now put in front of every "this is broken" ticket before anyone opens the code.

A bug is a contradiction. A missing spec is a silence.

A bug exists when the code does something the spec says it should not, or fails to do something the spec says it must. The rule is written somewhere, in a ticket, a requirements doc, an acceptance test, and the running code disagrees with it. That disagreement is checkable. You can point at the rule, point at the behavior, and show the gap between them.

A missing spec is different in kind, not degree. Nobody wrote a rule for this case at all. The code cannot be wrong, because there was nothing for it to be wrong against. Asking an engineer to "fix" it is like asking someone to correct a sentence that was never written.

The confusion happens because both show up in the same place: a ticket that says "this doesn't work right." The words are identical. The underlying problem is not.

Why the mix-up is expensive, not just untidy

When a missing spec gets logged and triaged as a bug, an engineer picks up the ticket expecting to find a fault in the code. They won't find one, because there isn't one. What they will find is a decision that has not been made, and most engineers, faced with a ticket that says "fix this," will make that decision themselves, quietly, under time pressure, without the person who owns the product outcome ever weighing in.

That is how a team ends up with behavior nobody chose on purpose. The discount-code case above got "fixed" by an engineer who decided, reasonably, that the code should apply only once per order. Nobody asked whether the business wanted that rule, or a different one, or wanted the code to prompt the customer. It shipped as a bug fix. Three months later, a different engineer touched the same area for an unrelated reason, saw the one-time rule, assumed it was intentional, and built on top of it. The undefined case never got resolved. It just got buried one layer deeper.

Cost escalation from undefined requirements is not folklore. A NASA and INCOSE study on error cost escalation across the project life cycle found that the cost of fixing an error grows at every stage between the requirements phase and operations, and grows fastest of all once a system is live. The mechanism is the one we saw with the discount code: an unresolved requirements gap doesn't disappear when it's left unaddressed. It gets built on top of, by someone who didn't know it was still open, and every layer added on top makes the eventual correction larger.

The decision rule: ask "is there a rule this violates?"

Before a ticket gets assigned to an engineer as a bug fix, ask one question: does a written spec, ticket, or acceptance criterion say what should happen in this exact case?

If the answer is yes, and the code does something else, it's a bug. Send it to engineering. The work is to find where the code diverges from the rule and bring it back in line.

If the answer is no, nothing says what should happen here, it's a missing spec. Sending it to engineering as-is asks the wrong person to make the wrong kind of decision. The work is to decide what the rule should be, and that decision belongs to whoever owns the product outcome: a product manager, a founder, whoever is accountable for what the customer experiences. An engineer can and should flag that the gap exists. Picking the actual behavior is a different job.

This question takes under a minute to ask and changes who does the next step entirely. That is the whole value of the rule: it is cheap to apply and expensive to skip.

The same question also tells you when a "bug report" is actually a request to change the spec, not to enforce it. A customer might say "the app is broken because it lets me submit a form with no email address." If the spec never required an email address, the app is doing exactly what it was told. The customer isn't wrong that the behavior is a problem. They're describing a spec they think should exist, not a bug in the one that does. That's still a missing-spec ticket, routed to the person who can decide whether to add the rule, not to an engineer hunting for a fault that isn't there.

Keep the two in separate queues

Once you can tell them apart, keep them apart. A shared "bugs" queue that mixes contradictions with silences guarantees the mix-up will happen again, because the two look the same in a ticket title and only differ once someone reads the spec closely enough to notice one doesn't exist.

Route a genuine bug to the engineer closest to that code. Route a missing spec to whoever can actually decide the rule, with the undefined case stated plainly: "the spec doesn't say what happens when X, here is what a customer hit." Do not reopen it against the engineer who happened to touch the code last. They didn't do anything wrong. There was nothing written for them to get wrong.

In the work we do, this is also where an eval suite earns its keep, and where its limits show up honestly. An eval checks code against a rule that was written down before the check ran, so it catches a real bug with precision: the code did X, the spec said Y, the eval fails. It cannot catch a missing spec on its own, because there's no rule yet to check the code against. What it can do is force the question. Writing the eval for a new case is often the exact moment a team notices the spec never said what should happen here, and that discovery is worth more than the eval itself.

Why AI-written code makes this distinction sharper, not smaller

When code is written from a specification, the line between the two categories gets easier to draw, not harder. Ask the code to do exactly what the spec says, and any place it doesn't is a genuine bug: a checkable gap between the instruction and the result. Ask what should happen in a case the spec never mentioned, and there's no instruction to check against at all, whether a person or a model wrote the code.

The temptation, when a model wrote the implementation, is to assume any unwanted behavior must be a bug in how the model interpreted the spec. Sometimes it is. Often it isn't: the model filled a genuine gap with a plausible guess, the same way the engineer in the discount-code example did, because something in that case had to happen and nothing told it what. The fix in that situation is not a better prompt or a retry. It's the same fix as any other missing spec: someone who owns the product outcome decides the rule, and the spec gets updated so the next implementation, human or model, has something to be checked against.

What this looks like on a real ticket

Take a support message: "the app shows the wrong total when I apply two coupons." Before assigning it, check the spec for coupon stacking. If the spec says coupons don't stack and the app stacked them anyway, that's a bug: find where the stacking logic slipped in and remove it. If the spec never mentions what happens with two coupons at once, the app didn't do anything wrong. It did something undefined, and the real question, does stacking discounts even make sense for the business, has not been asked yet, let alone answered.

The second case can look more urgent than the first, because a customer is unhappy in both. But urgency is not the same as clarity about what kind of problem it is. A missing spec resolved in a rush, by whoever is on call, is a decision made by accident. It will outlast the ticket that created it.

Thanks to the engineering and product teams who have walked us through their own ticket queues and were honest about how many "bugs" turned out to be decisions nobody had made yet. A bug is a promise the code broke. A missing spec is a promise nobody made, and no engineer can fix a promise that was never written down.

Sources

Common questions

What is the difference between a bug and a missing spec?

A bug is code that does something the written spec says it should not do, or fails to do something the spec says it must. A missing spec is behavior the spec never addressed at all, so there is no rule for the code to have broken, only a gap someone has to fill with a decision.

Why does it matter which one a ticket actually is?

Because the two need different people and different work. A bug needs an engineer to find where the code diverges from the rule and correct it. A missing spec needs someone with authority over the product to decide what the rule should be, and no amount of debugging produces that decision.

How can a team tell which one they are looking at, quickly?

Ask whether a written spec, ticket, or acceptance criterion says what should happen in this exact case. If one exists and the code does something else, it is a bug. If nothing says what should happen, the code cannot be wrong, because there was nothing for it to be wrong against.

What happens when a team treats a missing spec as a bug?

An engineer picks a behavior that seems reasonable and ships it as a fix, without anyone with product authority agreeing it is the right behavior. The ticket closes, but the decision was made by whoever happened to pick up the ticket, not by whoever should have owned it, and the same gap often resurfaces in a different form later.

Does "the code is technically correct" mean there is no problem?

No. Code can match its spec exactly and still produce an outcome nobody wants, because the spec itself had a gap. The fix is a decision about what the spec should say, made by whoever owns the product outcome, not a change to the code.

Who should decide what happens in a case the spec never covered?

Whoever owns the product outcome, not whoever is closest to the code when the gap surfaces. An engineer can flag that a gap exists and can implement whatever gets decided, but picking the actual behavior is a product decision with consequences the engineer is often not positioned to weigh.

Is this only a problem for large teams with formal specs?

No. Even a two-person team working from a short brief runs into this, because any brief short enough to write in an afternoon leaves cases undefined. The size of the team changes how the decision gets made, not whether the gap exists.

How should a team log an issue that turns out to be a missing spec, not a bug?

Separately from the bug queue, with the undefined case stated plainly and routed to whoever can decide it, rather than reopened against the engineer who touched the code last. Keeping the two queues distinct is what stops a missing-spec ticket from being picked up and closed as a code fix by mistake.

Does an eval suite help catch missing specs?

An eval suite catches bugs reliably, because it checks code against rules that were written down before the check ran. It cannot catch a missing spec by itself, because there is no rule yet to check against, though writing the evals is often the moment a team notices a case the spec never covered.

What is the cost of not making this distinction?

Time spent "fixing" code that was never wrong, a decision made informally by whichever engineer happened to touch the ticket instead of by whoever owns the product outcome, and a spec that stays incomplete because the gap got patched in code instead of written down.