Engineering Practice

"You Should Have Caught That Edge Case": How to Push Back

Your PM says you missed an edge case that was never written down. Here are the words that turn that into a story instead of a fight.

There is a conversation that happens on software teams so often it may as well be a ritual.

https://grizzlypeaksoftware.com/articles/api/image/442

Loop Engineering: Agents That Run While You Sleep

Loop Engineering: Agents That Run While You Sleep

Closed loops that discover work, verify results, and schedule their own next run — battle-tested patterns for Claude, OpenAI, and MCP. No toy demos.

Buy the Book!

The team works through a prioritized backlog. Stories get refined, estimated, accepted, and delivered. Then someone demos the increment, a project manager or product owner finds a behavior that nobody wrote down, and the sentence arrives: "You were supposed to build the whole app. You should have noticed this was missing and worked it into the requirements."

The team responds with the only fact available: "There was no story for that."

From there it goes one of two ways. Either the room gets quiet and the developers absorb the work, or the room gets tense and everyone leaves annoyed. Both outcomes are bad, and the reason they are bad is the same: nobody in the argument is talking about the actual problem.

The actual problem is that undefined work is being treated as free.

Below is the language I would use to shut that down without turning it into a fight, the dividing line that tells you when the PM is actually right, and the handful of practices that stop the argument from recurring every sprint.


The Argument Under the Argument

When a PM says "you should have caught that," they are usually not accusing you of laziness. They are expressing a real and legitimate frustration: the product does not work the way a customer will expect it to work. That frustration is valid. The product genuinely is incomplete.

The mistake is in the next step, where an incomplete product gets converted into an individual failure of diligence. That move is comfortable because it requires no process change and no scheduling conversation. Somebody missed something, somebody will fix it, and the plan stays intact on paper.

But the plan does not stay intact. The work still costs what it costs. It just gets paid for out of a budget nobody tracks: evenings, weekends, cut corners on the stories that were actually committed, or the slow erosion of test coverage. Scope that enters through the blame door never shows up in velocity, never shows up in a burndown, and never shows up in the conversation about why the date slipped.

This is the part I would push hardest on. The problem with absorbing undefined scope is not that it is unfair. It is that it makes the team's output unpredictable, and predictability is most of what a delivery organization is selling.


Get the Ownership Boundary Right Without Sounding Defensive

Roles on a healthy team are not a matter of taste. They are a division of decision rights.

The product owner or project manager owns the what and the order it gets built in. The development team owns the how and carries a hard obligation to surface risk, ambiguity, and technical constraint as early as it is visible. That second half matters. Ownership of the how is not a license to build exactly what the ticket says and shrug at the consequences.

What the boundary does mean is that engineers do not get to unilaterally decide that an undefined business behavior is worth building right now, ahead of everything else in the backlog, at the price of the work that was actually prioritized. That decision belongs to the person who owns priority. When a team is expected to invent, prioritize, and absorb undefined behavior while still hitting the original date, they have quietly been made product owners with none of the authority and all of the accountability.

That is the framing you want in your head before you open your mouth, because it converts a defensive posture into a procedural one. You are not arguing about whether you are good at your job. You are pointing out that a decision was made by default, and asking for it to be made on purpose.


Requirements Are Discovered, Not Delivered

Here is the assumption that breaks these teams: that requirements exist in complete form at the start, held by the business, and the only question is how faithfully engineering transcribes them.

That has never been true of any nontrivial system I have worked on. Requirements are discovered while building. You find the third state nobody considered because you had to write the state machine. You find the concurrency question because you had to write the transaction. You find the "what happens if they close the tab here" case because you had to write the flow. Building the thing is the process that generates the knowledge about what the thing needs to do.

If discovery is a normal, expected, unavoidable output of construction, then a missing edge case is not an anomaly. It is the system working. The only real question is what the team does with a discovery when it happens: convert it into a decision, or convert it into a grudge.


The Exact Words to Use in the Room

Say something close to this, and say it calmly:

"We're aligned on the goal: a complete, production-ready product. What we delivered matches the stories and acceptance criteria that were refined, accepted, and prioritized. When we hit behavior that isn't covered, our job is to surface it so it can be written down, sized, and prioritized against everything else in the backlog. That's what we're doing right now.

If we treat an undefined edge case as scope that was already committed, it displaces work that was committed, and we lose the ability to tell you what will actually be done by the date. Let's write down the expected behavior, create the story, and decide together where it goes. And let's tighten refinement so more of these surface before we're in the sprint."

Every sentence in that paragraph is doing a job.

It opens on the shared goal. You and the PM want the same outcome. Say so first, because the fastest way to lose this conversation is to let it become engineering versus product.

It states the facts without apology. No "sorry we missed it," because you did not miss it. There is nothing to miss when nothing was written. Apologizing here is not politeness, it is a concession that the standard was "read the PM's mind," and you will be held to that standard forever after.

It names the mechanism, not the person. Undefined scope displaces committed scope. That is arithmetic, not an accusation.

It ends by producing work. You are not refusing to build the edge case. You are refusing to build it invisibly. The output of the conversation is a story with a priority, which is exactly what the PM actually wanted.

It offers a process improvement. This is what keeps you from looking like someone hiding behind procedure. You are proposing to make the gap smaller next time.

One more thing: put the outcome in writing. Not as a paper trail for a fight, but because "we agreed this is a new story and it goes after the payment work" is the kind of decision that evaporates from memory in about four days and comes back later as "I thought you were handling that."


When the PM Is Actually Right

I am not going to pretend this is always a one-sided story, because it is not, and an article that pretends otherwise is useless to anyone who has to work with a PM tomorrow.

The PM has a real complaint when:

The team saw it and said nothing. If a developer noticed during implementation that logged-out users hit a dead end and decided it was not their story, that is a genuine miss. Surfacing gaps is part of the job. Not deciding them is where the line sits.

Refinement is a formality. If the team sits silently through refinement, asks nothing, and then invokes "there was no story for that" at demo, the process is being used as a shield rather than a tool. Silence in refinement forfeits some of your standing later.

The gap is inside the story, not outside it. "Save the record" implies validation, error handling, and a failure path. Not every unstated behavior is new scope. Some of it is craftsmanship, and treating a missing null check as a requirements failure will burn your credibility fast. Reserve the pushback for behavior that requires a business decision: what should happen, who is allowed to do it, what the user sees when it goes wrong.

The distinction I use is simple. If answering the question requires a business decision, it is a product gap and it needs a story. If answering it requires only engineering judgment, it is our gap and we should just handle it.

Being honest about that second category is what earns you the right to be firm about the first.


What Effective Teams Actually Do

Mature teams do not eliminate missing edge cases. They make them cheap to discover and cheap to decide. A few practices carry most of that weight.

A real Definition of Ready. Stories do not enter a sprint until acceptance criteria exist, the obvious edge cases have been discussed, and open questions are either answered or explicitly deferred with a name attached. Developers and testers participate, because a Definition of Ready written by product alone is just a wish.

Three Amigos refinement. Product, engineering, and test walk scenarios together before code exists. Testers are the highest-leverage people in that room and the most frequently left out of it. Their entire professional instinct is to ask "what happens if" and that is precisely the question the meeting exists to answer.

Gaps become backlog items, immediately. When something surfaces mid-sprint, it gets written as a story in the moment, not at the end of the sprint and not in someone's notebook. The product owner sets its priority. It does not get silently labeled "the team's miss" and folded into the current work.

An assumption log. When the team has to move forward without an answer, write down the assumption and where it was applied. This one is cheap and pays for itself the first time an assumption turns out to be wrong, because the conversation shifts from "why did you build it that way" to "we wrote this down on the 14th and nobody objected."

Explicit out-of-scope statements. Strong story sets say what is deliberately not included in this increment. Most teams document what they will build and leave the boundary implicit, which means the boundary gets drawn retroactively, in a demo, by whoever is most disappointed.

Track injected scope. Count the stories created after work started, or the points added mid-sprint. Not to build a case against anyone. To make the pattern visible. If a meaningful share of every sprint is requirements that arrived after the sprint began, that is not a team performance issue, it is a discovery process that is running too late, and it should be raised as an impediment with numbers attached rather than as a complaint with feelings attached.

Teams that push discovery earlier do exist, and some involve engineers deeply in the discovery phase. That works well. Even then, prioritization stays with product. The teams that struggle the most are the ones that treat requirements as a one-way handoff and then look for someone to blame when reality shows up.


This Gets Worse With AI in the Loop

Worth saying, because it is changing the shape of this argument right now.

When a team is generating a large share of its implementation with AI assistance, the bottleneck moves off typing and onto specification. Code arrives faster, which means the gaps in the requirements get exposed faster and in greater volume. An agent will happily build exactly the story you gave it, complete with a confident, idiomatic, fully-tested implementation of the wrong behavior for the case nobody defined.

The old excuse for thin requirements was that engineers would fill in the blanks while they wrote the code, slowly, using judgment. That buffer is shrinking. Precision about intent is now the scarce input, and teams that never built the muscle for writing down expected behavior are finding out that their process was being quietly subsidized by how long it used to take to build things.

If anything, the "you should have caught that" conversation is going to happen more often, not less. The teams that handle it well will be the ones who already made requirements a shared, explicit, written artifact.


The Real Goal Is Predictable Completeness

No team fully implements what was never defined or prioritized. Pretending otherwise gets you one of two products: an incomplete one, or a complete one built by a team that is quietly running on fumes and will be gone in six months.

The professional position is short enough to hold in your head:

We build what is clearly defined and prioritized. We surface gaps the moment we see them. We make the backlog more complete together, and we do not pretend undefined work is free.

That stance is not adversarial. It is the only version of the job where a date means anything.

Start with the cheapest piece of this. In your next refinement session, ask one question out loud for every story on the board: "what should happen when this fails, and who decides?" Write the answers into the acceptance criteria, and write down the ones nobody can answer yet. That list is your assumption log, and it is the artifact that ends this argument before it starts.