A pipeline that writes children's cartoons
I run a small studio where an agent pipeline produces short animated episodes for a children's channel: a language model writes the script as structured data, a text-to-speech provider reads it, and a renderer turns the whole thing into video. Nobody watches it happen. That is the point, and it is also the problem, because the only thing standing between a language model and a published video is a set of validation gates.
One of those gates checks a claim the script makes out loud. In an alphabet episode the narrator says something like "P, as in pillow", and the gate verifies that the word actually starts with the letter being taught. It is a small rule with a real job: an episode that shows a flower while saying it starts with P teaches a four-year-old something false.
Zero problems, all day
I shipped the gate and ran it across every episode the channel had produced. It reported problems in five of seven files: a prop that vanished mid-scene, a narrator repeating a character's line, two characters standing in the same place. Useful, specific, actionable. Exactly what a new gate should look like.
The letter rule, specifically, reported nothing. I had a flower labelled as starting with P sitting in one of those episodes, so I went looking. The pattern printed correctly in the source:
_LETTER_LIKE = re.compile(r"\b([a-z]{1,6})\s+jak\s+([a-z]+)", re.IGNORECASE)
It compiled. Its flags were right. Applied to text that obviously matched, it returned an empty list. So I printed the compiled pattern back out, as characters:
repr: '\x08([a-z]{1,6})\\s+jak\\s+([a-z]+)'
kody: ['0x8', '0x28', '0x5b', '0x61', ...]
0x8. The pattern did not start with a word boundary. It started with a literal backspace character, because somewhere in the editing that produced the file the string had stopped being raw and \b had been interpreted as the escape it is in an ordinary Python string.
The regex was therefore asking for text containing a backspace, followed by a short word, followed by "jak". No document in the world contains that. The rule could not match anything, ever, under any input.
Silence has two causes and one appearance
Nothing about this looked wrong. The module imported. The pattern compiled without a warning, because a backspace is a perfectly legal character in a regular expression. The gate ran on every episode and returned an empty list of problems, which is precisely what a gate returns when the content is fine.
That is the whole shape of the bug. A working check on clean input and a dead check on dirty input are the same event from the outside: no output. Every other gate I wrote that day was firing, which made the silence read as good news rather than as an absence of news.
It would have shipped. The next automated run would have written an alphabet episode, put a flower on screen under the letter P, passed the letter rule, rendered, mastered, and left a finished file with a green checkmark next to it.
Running the gate on known-bad input
I did not find this by reading the code. I found it because I had a rule about new gates: before believing one, point it at material I already know is broken and confirm it complains.
I had that material. A reviewer had watched the episodes and written down exactly what was wrong with each one, in plain language, days earlier. So the test was not synthetic and it was not a fixture I invented to match my own assumptions. It was seven real files and a list of seven real defects, and the question was simply whether the gates found the things a person had already found.
Six of seven defects were caught. The seventh, the flower, was not, and that gap is what sent me to the pattern. The episode a reviewer had praised came back with zero problems, which was the other half of the evidence: the gates were not simply firing at random.
A new check is a hypothesis until it fails on purpose
The general version, which I now apply to anything that reports problems: a check you have never seen fail is a check you have never seen work.
In practice that means three cheap habits:
- Keep the bad input. When something is wrong in production, the artifact is worth more than the fix. It is the only test case you did not have to imagine.
- Assert the positive. A test that says "no problems on clean input" passes for a deleted function. Pair it with one that says "exactly this problem on this dirty input".
-
Print what compiled, not what you wrote. Source and runtime disagree more often than you expect, especially anywhere a string passes through a template, an editor macro or a shell. Two lines of
repr()would have shown me the backspace immediately.
This is the same failure class as a monitoring dashboard that is green because its data source stopped: the indicator is not reporting health, it is reporting nothing, and nothing renders as fine. I wrote about three production outages with that exact shape in an earlier post. This one was cheaper only because it was caught before it reached anybody.
The gate found the bug twice
Once the pattern was repaired, it flagged the flower in the alphabet episode and flagged it again in the weekly recap film, which had inherited the same claim. Two independent rules caught it: one comparing the props on screen against the letter being taught, one parsing the spoken claim itself.
That redundancy was not planned. It is simply what happens when you write the rule as a statement about the world rather than as a statement about one file format, and it is the reason I now prefer gates that check claims over gates that check structure.
Originally published at neuragrowth.co. I run a one-person digital-products studio and write up what breaks in production.
If you write CLAUDE.md files, I keep a set of working templates here: neuragrowth.co/free/claude-md-templates.
Top comments (0)