DEV Community

Ted
Ted

Posted on Originally published at tedagentic.com

The Error Was Real. The Page Was Fine.

The report arrived from the hosting platform's AI monitoring, and it was admirably specific.

A listings page on one of my sites, it said, always falls back to a degraded list. The main database query fails every time, because it asks for a field that doesn't exist. The page silently drops to a simpler query, so the listings load without their city, state and country — which can break the grouping, the filters and the labels visitors see. It had errored 28 times in 24 hours.

That is a good report. It names the symptom, the mechanism, the consequence and a count. I nearly went straight to the consequence and started looking for broken labels.

The first half turned out to be exactly right. The second half never happened.

The query had never worked

The page asks the database for its listings along with a nested relationship: each listing's city, that city's state, and that state's country. In Supabase that is one request, written as a nested select:

supabase
  .from('hotels')
  .select(`
    *,
    cities:city_id (
      name, slug,
      states:state_id (
        name, code,
        countries:country_id ( name, code )
      )
    )
  `)
Enter fullscreen mode Exit fullscreen mode

I ran that exact request against the live database and got this back:

HTTP 400
{"code":"42703","message":"column countries_3.code does not exist"}
Enter fullscreen mode Exit fullscreen mode

Neither the states table nor the countries table has a code column. The database reports only the first missing column it trips over, so the error names countries, but both references were wrong. The request was malformed as written, so it could not succeed on any load, for any visitor, ever.

Wrapped around it was a pattern you have probably written yourself:

const { data, error } = await tryWithRelationships();
if (error) {
  console.error('relationship query failed, falling back to simple query:', error);
  return await supabase.from('hotels').select('*');
}
Enter fullscreen mode Exit fullscreen mode

Try the rich version first. If it fails, degrade gracefully. It reads like defensive engineering, and it is. It is also, in this case, a description of a code path that had executed on every page load since the day it shipped. The "rich version" was not the primary path with a safety net beneath it. It was a request that failed, followed by the actual implementation.

Then I checked what visitors saw

The report's consequence — listings without their location — is a reasonable inference from that error. The failing request is the one that fetches location. If it never returns, location should be missing.

Except the listings table stores its own copy. Each row carries plain city, state and country columns, and those columns were populated on all 58 rows. The fallback's select('*') returns them. And the code that formats each listing already reached for them:

const city  = row.city || row.cities?.name || 'Unknown';
const state = row.cities?.states?.name || row.state || 'Unknown';
Enter fullscreen mode Exit fullscreen mode

It uses the relationship when it exists and the row's own column when it doesn't. That had been written deliberately, with a comment explaining why: only 8 of the 58 rows had a city_id set at all. So for 50 listings the relationship would have come back empty even on a day the query worked. Whoever wrote the formatter had already stopped trusting the relationship and routed around it.

So every visitor got the fallback, and the fallback carried everything the page needed. The grouping worked. The filters worked. The labels were right. The error fired on every single load, and nobody who loaded the page ever saw it.

An error rate counts what your code tried and failed. It does not count what anyone saw. When a fallback is doing its job, those two numbers stop having anything to do with each other.

The mirror image of the usual problem

I have written before about a fallback chain that did the opposite. Four layers of image lookup, where a dead link in layer three was simply covered by layer four, and forty-nine broken URLs sat behind a default photo without a single error being raised. That was silence hiding damage.

This was noise hiding the absence of damage. Same architecture, the same defensible habit of degrading instead of crashing, and the signal failed in the other direction. A fallback can make a real failure invisible. It can just as easily make a harmless failure look catastrophic, because the thing that fails is loud, and the thing that catches it is quiet.

That's what the report got wrong, and it was a good-faith mistake. It read the error, which is about a query, and inferred the impact, which is about a page. The error message was completely accurate about the query. It simply contained no information about the page, because the page's outcome was decided one step later, by code the error never passed through.

What it actually cost

Not nothing, which is worth saying plainly, since "the page was fine" is not the same as "there was no bug."

  • A failed request on every load. 28 in a day is 28 wasted round-trips and 28 console errors, each one burying whatever real error might arrive next to it.
  • A code path that misrepresented itself. Anyone reading the component would believe the page had a richer mode it used when it could. It never had one. Code that describes behaviour it doesn't have is a trap for the next person who reads it.
  • One field genuinely degraded. Country had no column fallback. It read row.cities?.states?.countries?.name || 'USA', so every listing was labelled with a hardcoded default.

The fix for the error was deleting two field names from the select. The request now returns 200 and resolves the relationship for the 8 rows that have one.

The country field was the part that didn't go the way I expected. With the query fixed, I checked what came back for those 8 rows, expecting real country names at last. Every one of them was null. The states table has a country_id column, and it is empty on every row. So the country relationship cannot resolve for anything, and the label still falls back to 'USA'. It happens to be correct today, because every listing is in the US. It will quietly be wrong for the first one that isn't.

That is the richer mode, working exactly as written, with nothing underneath it.

The useful version of the report

I don't think the lesson is "distrust automated monitoring." The monitor found a real defect that had been live for as long as the page existed, and I would never have looked otherwise.

The lesson is that an error with a 100% failure rate and zero visible impact is not a contradiction to be explained away. It is a finding in its own right, and a sharper one than the report made. If something fails every time and nobody notices, then nobody depends on it. That path is dead code wearing the costume of the main path.

So when an alert tells you something fails on every request, ask the second question before reaching for the consequence: what did the user get instead? If the answer is "exactly what they needed," then the error isn't telling you the page is broken. It's telling you the part you thought was the system has never actually run — and that the part you thought was the backup is the system.

The number was true. The story attached to it was a guess.

Top comments (0)