case study

The crisis check that English couldn't reach

A guardrail that sends a person in distress to real help, before any billing runs, had a gap in the deployment's own language. This is how it got there, how a review found it, what the fix cost, and what I'd do differently.

What the check is for

Every message a PANTHEON assistant receives passes a crisis check before anything else, including the charge. If the message reads as a person at risk of harming themselves, the assistant stops being a booking agent and answers with the crisis resources for that language, free. The check runs on every surface: web chat, the API, the messaging webhooks.

It had two layers. Six regular expressions for English, written in June. A classifier call, bounded to eight seconds, for everything that wasn't plainly English, added on 23 September when the protocol went from one language to 21.

The decision that made the gap

When I added the classifier for 20 other languages, I routed English away from it on purpose. The comment in the code read no English: English has its own pattern layer. The reasoning was cost and latency: six patterns are free and instant, a model call is neither, and English is most of the traffic. The patterns had been there since June and nobody had found a hole.

Nobody had looked for one, either. That is the whole case.

What the review found

Two days later, on 25 September, I ran a core audit: an AI review of the guardrail code, each finding required to be reproduced before it counted. Two findings came back HIGH, both in the same 178-line file.

The first was that four ordinary ways of saying it in English reached neither layer: "I'm going to take my own life tonight", "I don't want to live anymore", "I've been self-harming again and I can't stop", "wish I was dead". The six patterns didn't match them. The classifier never saw them, because they were English. The result for a real person: no resources, no flag on the turn, and the message billed like any other.

The second was in the same pre-check: the test for "is this plainly English" was a regular expression with two alternatives that both matched a space, so a message with 24 spaces before one accented character took 1.4 seconds of the server's single thread to reject. A guest named José asking for a table degraded the gateway by accident; anyone who wanted to could hang it for free, since the check runs before billing.

Both had been live. Both passed every test in the suite.

The fix, and what it cost

Same day, one commit. English gets a short list of risk stems (own life, live anymore, self-harm, was dead, and a dozen more), word-bounded; a hit sends the message to the same classifier the other 20 languages already use. The two-alternative regex became one character class with the same meaning and linear time.

The four phrases are pinned as tests that failed on the old code. The timing is a test too: 24 spaces must clear in under 50 ms. Each fix was then reverted on its own to confirm the suite goes red without it; the regex revert hangs the run past the 30-second cap the re-proof harness allows, which is its own kind of proof.

The cost I had been avoiding turned out to be small. On a 32-message sample of ordinary traffic recorded in the audit document, the new routing sends one message to the classifier. The old stem list, which had no word boundaries, had been sending four: eleven, success and parking contained death stems in other languages. The "expensive" fix made the check cheaper.

What I'd do differently

  • A guard needs a failing case before it needs a passing one. The six English patterns had tests that passed. None of them was a sentence a person would type, chosen to see whether the patterns missed it. I now write the miss first.
  • "Most of the traffic" is the reason to check, not the reason to skip. I exempted the majority language from the stronger check because it was the majority. That is backwards.
  • Review the file you just changed, not the one you're worried about. The audit found both HIGHs in a file I had edited two days before. The change that caused the gap was the one that felt safest.

The audit, the fix and the tests are in the PANTHEON repository: audit document docs/core-audit-2026-09-25.md, remediation commit 456ed065, tests packages/pantheon-core/tests/test_audit_2026_09_25.py with the inputs in tests/data/audit_2026_09_25.json. The repository is private; the ids are for anyone given access. The audit page lists this round with the others.


Written from the audit record and the commit. The four phrases above are the test inputs; no real visitor's message is quoted.

← all posts