Back to News & Insights
Web Development September 28, 2026 · 4 min read

Three bugs that reported success: what "exit 0" doesn't tell you

Last week I wrote about shipping a paid API for AI agents and getting two real calls in a week. The...

Three bugs that reported success: what "exit 0" doesn't tell you

Last week I wrote about shipping a paid API for AI agents and getting two real calls in a week. The follow-up was supposed to be about fixing the product. It turned into something else, because in the process of fixing it I hit three bugs in a row that had one thing in common:

That turns out to be the failure mode that costs the most time, and I don't see it discussed much, so here they are.

I was adding debarment records — public sanctions against companies barred from public procurement — to my business verification API. The endpoint took a parameter called inqryDiv. I passed 1.

It returned HTTP 200 with an empty result set. No error, no warning, no hint. The obvious reading: this company has no sanctions on record.

The correct value was 2. With 1, the API answers a question nobody asked and returns nothing, forever, for every company.

If I'd shipped that, my API would have told users "no sanctions found" for every single company in Korea, with a straight face and a 200 status code. That's not a broken feature. That's a feature that confidently lies.

What saved me: I happened to test against a company I knew was sanctioned. If I'd tested with a random company — which would also have returned zero, correctly — I'd never have caught it.

I built a scheduled job to enrich my index with registration numbers. Registered it with Cloud Scheduler. Ran it once manually before trusting the schedule.

The entry point had a guard to detect whether the module was being run directly, and it built the comparison URL by hand:

js // Works on my machine. Not on Linux. if (import.meta.url === file:///${process.argv[1]}) { main(); }

On Windows, process.argv[1] looks like C:\path\to\file.js, so prefixing file:/// is right. On Linux it's already /app/dist/job.js, so you get file:////app/dist/job.js — four slashes — which never matches. The guard silently evaluated false, main() never ran, the process exited cleanly, and the platform reported success.

The fix is one line (pathToFileURL(process.argv[1]).href), but that's not the interesting part. The interesting part is that a green checkmark in a scheduler dashboard is not evidence that your code ran. It's evidence that a container started and stopped without crashing. Those are very different claims.

If I had trusted the schedule instead of running it once by hand, I would have seen green checkmarks every morning for a week while the index sat untouched, and I'd have gone looking for the problem in the API, the credentials, the data — anywhere but the one line that decides whether the program starts at all.

With the job fixed, I tested search with Korean company names over curl in Git Bash. Zero results. Every time. English names worked fine.

I was minutes away from digging into the normalization pipeline — Korean text has its own encoding pitfalls, so a bug there was entirely plausible — when I ran the identical query from a local Node script instead. It worked.

The server was fine. Git Bash was mangling the UTF-8 in the URL before it ever left my machine.

This one cost the least time but taught the sharpest lesson: when a test fails, the test environment is a suspect too. I'd been treating my terminal as a neutral observer. It wasn't.

Three different layers — a government API, a container platform, a shell — and the same shape each time. Something went wrong, and every signal I had available said things were fine.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation