DEV Community

Cover image for Why the Same Bug Keeps Coming Back - And How to Stop It
Deepanshu Kumar
Deepanshu Kumar

Posted on

Why the Same Bug Keeps Coming Back - And How to Stop It

A few months ago, I fixed a bug on a Friday afternoon. Status filter on a dashboard widget returning empty results for some users. Found the problem, added one line, tests passed, shipped it. Felt good about it.

The following Tuesday someone filed another ticket. Different widget, same symptom. Assignee filter, empty results, same users affected. I opened the file, found the missing line in about thirty seconds, and just sat there for a moment.

I hadn't fixed the bug. I fixed one place where the bug lived. The same broken pattern was sitting in three other handlers, and I hadn't thought to check.


What actually happened

The app was a multi-tenant SaaS where users have permissions scoped to specific warehouses. Dashboard widgets query orders, but only the ones the user is allowed to see. Simple enough.

We had six of these query handlers. Someone had written the first one correctly, copy-pasted it five times as new widgets were added, and somewhere along the way one important line kept getting dropped.

// Priority widget handler — written correctly
public async Task<List<Priority>> Handle(GetPrioritiesQuery query)
{
    var permittedWarehouses = await _warehouseService.GetPermittedWarehouseIds(query.UserId);
    permittedWarehouses.Add(null); // include orders with no warehouse assigned

    return await _repository.GetPriorities(permittedWarehouses);
}

// Status widget handler — the one that broke
public async Task<List<Status>> Handle(GetStatusesQuery query)
{
    var permittedWarehouses = await _warehouseService.GetPermittedWarehouseIds(query.UserId);
    // that null append never made it here

    return await _repository.GetStatuses(permittedWarehouses);
}
Enter fullscreen mode Exit fullscreen mode

The null append matters because some orders aren't assigned to any warehouse. Without it, those orders get filtered out entirely. For users whose entire order history happened to be unassigned, the widget showed nothing at all.

One line. Easy fix. But I only fixed it in the one handler that had broken visibly.

When I finally audited all six:

GetOrderStatuses     → ❌ missing null append
GetOrderPriorities   → ✅ has null append  
GetOrderAssignees    → ❌ missing null append
GetOrderLocations    → ✅ has null append
GetOrderCategories   → ❌ missing null append
GetOrderCosts        → ✅ has null append
Enter fullscreen mode Exit fullscreen mode

Three broken, three fine. The broken ones just hadn't surfaced yet, either because fewer users hit those widgets or because the affected users had quietly assumed the data wasn't there and moved on without filing a ticket.


Why this keeps happening

The code didn't start inconsistent. Someone wrote the first handler carefully. The inconsistency came from copy-paste and the very reasonable assumption that "this is basically the same as that other handler." Nobody set out to leave three bugs behind.

The trap is how bugs announce themselves. They show up as individual tickets, individual screens, individual user complaints. The natural instinct is to find the broken thing and fix it. That instinct is right, but it stops one step too early.

In my case the pattern was "handlers that filter by warehouse permission don't consistently include unassigned orders." I fixed one handler. I should have searched for the pattern and fixed the category.


The one that never got reported

After I found the other three broken handlers, I thought about GetOrderCategories. It had been broken since the original commit. Same missing line, same bug. No ticket had ever been filed for it.

Two explanations. Either none of our users had an order history composed entirely of unassigned-warehouse orders filtered by category. Or some of them had, looked at the empty widget, assumed the data wasn't there, and closed the tab.

I don't know which. Probably both, at different customers, at different times. The second one bothers me more than the bug itself. A real user looked at a broken screen, formed the wrong mental model of their own data, and moved on without telling anyone.


Before you close the PR

The habit that changed how I work: before writing the fix, write down what's wrong in one sentence of plain English. Not which file, not which method. The rule that got violated.

"GetOrderStatuses doesn't append null to the warehouse list" is a file reference. "Warehouse-permission queries don't consistently include null to catch unassigned orders" is a pattern. The second version tells you what to search for everywhere else.

Then search:

grep -r "GetPermittedWarehouseIds" --include="*.cs" .
Enter fullscreen mode Exit fullscreen mode

Read every result. For each one: should this also have the fix? If yes, it goes in the same PR. Not a follow-up ticket, not later. The same PR, while the context is fresh and the intent is clear.

The audit took me twenty minutes. The follow-up ticket that would have found GetOrderAssignees and GetOrderCategories would have taken two separate Tuesdays. That math is easy.


The bug that got reported was the one someone noticed. The ones that didn't get reported were waiting for the right user, the right data, or someone patient enough to sit with empty results and eventually wonder why.

Until next time,
Deepanshu

Backend engineer writing about production bugs, distributed systems, and engineering patterns learned the hard way.

Portfolio · Medium · LinkedIn · GitHub

Top comments (0)