A single except line meant to quietly skip cache corruption also swallowed the case where the dependency library wasn't even installed
This is the English version of a post originally written in Korean for my algorithmic trading system devlog(new tab).
A few days ago I found out that a module I'd added eight days earlier had never worked correctly since it was created.
There were no error logs. No crashes. It was just quietly returning empty values the whole time.
What happened
The module existed to fix a specific problem.
For some tickers, the actual number of shares outstanding wasn't available, so the system fell back to a rough estimate based on face value. That estimate sometimes diverged sharply from the real number, which occasionally made a stock's valuation ratio come out far lower (looking cheaper) than it actually was.
So I built a new module to cache and look up the real share count directly. Once it shipped, nothing looked wrong. No errors, and a separate hold condition meant there was no urgent reason to dig back into it right away.
A few days later, while working on something unrelated, I happened to print out an actual lookup result. Out of 100 tickers queried, zero came back with a value. It had never returned a single value since it was created.
What I thought at first
My first suspicion was the data itself — that the cache file was empty or corrupted.
It wasn't. The underlying data was fine. Thousands of tickers all had valid values, and reading the same file from a different execution environment worked without any issue.
So this wasn't a data problem. It only failed to load "in this particular production environment."
The real cause
The cause was a single line inside the loading function: except Exception: continue.
The original intent of that except clause was "if one cache file is corrupted, keep reading the rest." It was written to target one very specific failure: cache corruption.
The problem was that this production environment didn't have the library needed to read the cache file's format installed at all. When that happens, opening the file doesn't raise "this file is corrupted" — it raises a completely different kind of exception: "the engine to even read this file doesn't exist."
except Exception doesn't discriminate. It catches both. Cache corruption and a missing engine were swallowed the same way, silently. As a result, the function always returned an empty mapping, and the lookup always returned nothing.
What made it worse: the caller was also written to be lenient about failure. If it couldn't get a value, it silently fell back to the old face-value-based estimate — the exact problem the new module was supposed to fix. The module I built to fix that bug sat quietly useless for several nights running, while the system kept using the old, inaccurate estimate without a single warning.
How I fixed it
The first thing I did was split the exception by type.
Exceptions caused by the missing library got classified as an "environment defect" — logged loudly and re-raised (fail-loud) instead of swallowed. I also made sure this particular failure wasn't cached, so that once the library got installed, the very next lookup would work correctly without needing to restart any process.
The rest of the exceptions — the cache-corruption case this was originally written for — stayed silently skipped, as before. Making that path fail-loud too would mean one corrupted file could take down the whole thing, which would have broken the behavior it was meant to protect.
Then I added tests to make sure this split doesn't quietly regress again — one simulating a missing library to confirm the exception propagates, and one simulating cache corruption to confirm it's still skipped silently.
The general lesson
except Exception doesn't catch the one failure you had in mind — it catches every failure that's possible at that point in the code. In this case, a broad except written to guard against "one corrupted cache file" ended up swallowing a completely different class of failure (a missing dependency) too. Whenever you catch an exception, it's worth double-checking that you're really only catching the type you intend to.
When the installed packages differ between dev and production, code that was only verified in dev can be silently dead in production. It worked fine locally, and only failed in the environment that actually mattered. If you add a new dependency, it's worth separately confirming it's actually present wherever the code will really run.
"It runs without errors" is not evidence that "it works." This is especially true when there's a safety fallback that quietly reverts to the old logic on failure — that fallback can trigger every single time and still look completely fine from the outside. After wiring in a new module, it's worth directly confirming, at least once, that the new path is actually the one being taken.
If you wrote new code to fix a bug, whether that code is actually running is a precondition for saying the bug is fixed. Shipping it is not evidence by itself.
Top comments (0)