The Most Expensive Errors Are the Quiet Ones

On 25 April 2026, an AI-assisted code change took down the entire platform.

Not a

  • 500 error.
  • maintenance page.
  • timeout.

Both localhost and production simultaneously returned an empty white page with no content, no headers worth reading, and no indication of what had happened.

  1. The system was running.
  2. The system was also completely broken.
  3. It had nothing to say about the difference.

What made the incident more interesting was that neither AI nor automated testing caught it. The change had been made during an AI-assisted development session, and Playwright couldn’t help because PHP never reached the point where an application existed to test.

The site didn’t fail loudly.

It failed silently.

The correct setting and its consequence

`display_errors` is off in production for good reason.
You do not want PHP error details rendered in a user’s browser — stack traces, file paths, variable contents. The information is useful to a developer and useful to an attacker. Turning it off is standard practice.
What the setting creates, as a side effect, is this: a fatal parse error produces no output at all. Not a degraded response. Not a fallback. The server receives the request, PHP attempts to parse the file, encounters an error before a single byte of output is generated, and returns nothing. The page is blank because PHP never reached the point of producing a page.
A site that is completely non-functional looks, from the outside, like a site that is loading slowly.

Debugging from first principles

No error message means no starting point. The diagnostic sequence ran in order:
Is the server running? Apache was up, responding to requests. Not a server issue.
Is PHP responding at all? A test file with a single `phpinfo()` call returned nothing. PHP was not executing.
Is it one file or every file? Every file. The failure was not in a single route or feature — it was at the PHP layer, before any application code ran.
Was there a configuration change? No. The server configuration was unchanged.
Was there a code change? Yes. Three library files had been edited in the same session: `fn_audit_cron.php`, `fn_comms_mailread.php`, `fn_engine_analytics.php`.
Each of those files contained the same class of error. HTML table attributes — `scope=”col”`, `class=”header”` — had been written inside PHP double-quoted strings. In a PHP double-quoted string, the inner double quotes end the outer string. The parser sees the attribute value as code, cannot parse it, throws a fatal error, and produces no output.
Three files. One missing backslash each. The fix, once found, took seconds.

What finding it took

The fix is trivial. The path to the fix is not.
A blank page with no error, no log entry, and no signal is a puzzle that requires elimination rather than diagnosis. Each step in the sequence above took time — not because the steps were complex, but because there was nothing to narrow from. You are not following a trail. You are checking every possible cause in descending order of likelihood until something changes.
The irony is structural. `display_errors = off` is correct precisely because it withholds information from the wrong audience. The same setting that protects users in production withholds information from the developer who needs it most.
The compensating control — logging errors to a file rather than suppressing them entirely — was not in place yet. The file was blank because the errors went nowhere.

What the incident produced

One rule, now permanent in CLAUDE.md:
> *Never put unescaped double quotes inside a PHP double-quoted string. HTML attributes like `scope=”col”` inside `echo “…”` cause a fatal PHP parse error that blanks the entire site with no logged error.*
CLAUDE.md is loaded automatically at the start of every AI session. The AI that introduced the pattern — or that might introduce it — encounters this rule before it writes a line of code.
The incident took the whole platform down. The rule it produced is one sentence.
The sentence will not need to be rediscovered.
AI changes the speed at which both solutions and mistakes can be introduced. It does not remove the need for observability, review, or institutional memory. If anything, it makes them more important.

The class of failure

Production incidents fall into two categories. In the first, the system fails loudly: a 500 page, an exception in the logs, an alert from the monitoring. The signal is unpleasant but it is a signal. Debugging has a starting point.
In the second category, the system fails silently. The output is absent. The logs are clean. Everything looks correct except that nothing works. This is the more dangerous class of failure — not because it is harder to fix once found, but because nothing points you toward finding it.
Silent failures require a different kind of preparation. Before an incident: error logging to file, monitoring that detects blank responses, alerting on zero-output requests. After an incident: documentation precise enough that the cause cannot recur without being immediately recognised.
The blank page on 25 April produced nothing visible while it was happening. It produced one sentence of documentation after it was resolved. That sentence is now part of the institutional knowledge of the system — carried forward into every AI session, visible to every developer who reads CLAUDE.md.

A system that fails silently is not less dangerous than one that fails loudly. It is more dangerous, because it looks like a working system right up until it doesn’t.

The answer is not to make silent failures impossible. It is to make sure they cannot go unrecorded.

AI accelerated the mistake. The system’s lack of signal determined how long it took to understand it.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.