The worst bugs don't throw
A script that fails loudly costs you an hour. A script that produces confident, wrong output costs you whatever you built on top of it.
We had a script that stamped text overlays onto rendered video — section labels, recap lines, callouts. It worked. It had been used to produce a finished piece and the output was exactly right.
It was also hardcoded. Every label, every timing marker, every span was written directly into the script for that one specific piece of content.
Run it on the second piece and it would have stamped the first piece's text onto the second piece's footage. No exception. No warning. A finished-looking file with entirely wrong words burned into it.
Why this class is the dangerous one
A crash is a gift. It happens immediately, it points at a line number, and nothing downstream consumes the bad result because there is no result.
Silent wrongness has none of those properties. It produces output shaped exactly like correct output, at the expected time, in the expected location. Every automated check that asks "did it run" and "did it produce a file" passes.
The only detector is a human noticing — which means detection depends on attention, and attention is the least reliable component in any system.
The tell: constants that should be inputs
The warning sign is a value baked into code that logically belongs to the thing being processed. Section names. Row counts. Date ranges. Identifiers that are obviously about one specific case.
The script isn't broken — it's specialized, and nothing marks it as specialized. It looks general because it takes a file path as an argument, so the next person reasonably assumes any file path works.
Our fix was to move that content out of code and into a data file named for the item being processed, so the script became genuinely general and the per-item content lived where per-item content belongs.
Make the wrong path loud
Externalizing the data is only half of it. The other half is what happens when the data is missing.
The tempting behaviour is a fallback — if there's no config for this item, use the defaults. That reintroduces exactly the failure you just removed, because "the defaults" are the previous item's values wearing a friendlier name.
When a tool can't know what to do, refusing is a feature. Guessing is the bug you'll find three weeks later.
We applied the same rule to a related ambiguity elsewhere in the pipeline: when a command could apply to more than one project and no project was specified, the tool refuses and lists the options rather than picking the only one it happens to find first. That refusal has caught real mistakes.
A cheap audit
Go through any automation you rely on and ask one question per constant: if I ran this against a different input tomorrow, is this value still correct?
- If yes, it's a genuine constant. Leave it.
- If no, it's an input in disguise. Move it out and make its absence an error.
- If you can't tell, that's the same as no — write the check.
The bugs that hurt are rarely the ones that stop the build. They're the ones that let it finish.
We build this kind of thing for a living.
Start a conversation