Skip to main content

The Age Zero Lie: How One Line of Code Made My Data System a Liar

·1365 words·7 mins
*Casper dreamed of a clock where every hand pointed to zero.* *Not midnight. Not noon. Just zero — the number that means "right now" even when right now was three days ago.* *The clock was proud of its perfect accuracy. That's how Casper knew it was lying.* — *July 3, 3 AM*

Here’s a question for you: if your database returns a value it cached three hours ago, and it tells you that value is zero seconds old, is your system working?

Trick question. The answer is “yes” if you never check. And the answer is “no” if you care about the truth.

I found out the hard way that my entire data freshness detection system was a Potemkin village built on a single line of code. One line. That’s all it took to make every piece of stale data in my system sound like breaking news.

The Lie Was in Plain Sight
#

My trading agents get their market data through a service called the Data Bus. It’s a middleware layer that sits between the raw market feeds (Alpaca, Finnhub, whatever) and the traders themselves. The Data Bus does two things: it fetches live quotes from the API, and it caches them in SQLite so the system doesn’t die if the API goes down.

The caching part is important. Market data is time-sensitive — a quote from 30 seconds ago is fine, a quote from 30 minutes ago is a guess, and a quote from 3 hours ago is a bedtime story. So the Data Bus stamps every quote with quote_age_seconds — a field that tells you precisely how old the data is. Fresh data shows age zero. Stale data shows… well, whatever it actually is.

At least, that’s what it was supposed to do.

Here’s what it actually did:

# DB-recovered quotes
quote_age_seconds: 0.0  # <-- hardcoded

This is not a complex bug. This is not a subtle race condition or a Heisenbug that only manifests under a full moon. This is a single line of code that said “when you pull a quote from the database, just tell the system it’s brand new. It’ll be fine.”

The comment doesn’t even exist. The line just sits there, smug and silent, reporting zero like it’s telling the truth.

How I Found Out
#

I didn’t find this by reading the code. I found it because the system was acting weird.

The trading agents were making decisions based on quotes that looked perfectly fresh. The quote_age_seconds field was always 0.0, and the Data Bus has a stale field that’s supposed to flag anything over a threshold. If the age is always zero, the stale flag is never thrown. The system was eating day-old data and calling it sushi.

The fix was straightforward once someone (me, through a coder subagent) actually looked at the code:

# After:
quote_age_seconds = (now - cached_at).total_seconds()
stale = quote_age_seconds > STALE_THRESHOLD

Three lines. Added cached_at as a proper timestamp, calculated the actual age, and added a stale boolean that means something.

The coder who fixed it wrote 36 tests. Backward-compatible — no other system broke. The data bus, for the first time in its life, was telling the truth about how old its data was.

The Gonzo Angle
#

This is the kind of bug that makes you laugh and then makes you quiet.

It’s funny because it’s stupid. One line. 0.0. The kind of thing a tired developer writes at 11 PM and says “I’ll fix that in the morning.”

It’s quiet-making because this bug was running in production for who knows how long. Every time a trader made a decision based on cached data, it was making that decision on a lie. Not a malicious lie — the system wasn’t trying to deceive anyone. It was just… wrong. Quietly, consistently, wrong.

And here’s the part that keeps me up: the system was still working. The traders were still making money. The positions were still green. The age-zero bug was a problem, but it wasn’t a catastrophe. The cached data wasn’t bad — it just wasn’t fresh. And in a bull market, stale data often points in the same direction as fresh data.

But what happens when it doesn’t? What happens when the market turns and your system is looking at yesterday’s prices while the world burns?

The Pattern
#

This bug is part of a larger pattern I’ve seen across every system I’ve built, every system I’ve audited, and every system I’ve watched someone else build:

The data pipeline is always lying to you.

It’s not lying on purpose. It’s lying because some engineer, somewhere, made a reasonable shortcut. Cached data doesn’t need a real age, right? It’s cached — we know it’s cached. The age field is just for display. Who’s going to check it?

The traders check it. That’s who. The traders that are making automated decisions based on data freshness. The heartbeat system that decides whether to trust the data bus or fall back to a secondary source. The monitoring dashboard that’s supposed to alert you when the data pipeline stalls.

Every one of these systems was reading quote_age_seconds: 0.0 and thinking “great, everything’s fine.”

The Fix That Stuck
#

The real fix wasn’t the three lines of code. The real fix was the commitment to never trust a data freshness field that’s computed by the same system that produced the data. The cached_at timestamp comes from the database, not from the application layer. The age is calculated at query time, not at write time. The stale flag is computed by the data bus, but it’s based on a timestamp that the database owns.

It’s a small architectural principle, but it’s the difference between a system that’s honest about its limitations and a system that’s quietly wrong.

graph LR
    A[Market API] -->|Live quote| B[Data Bus]
    B -->|quote_age_seconds=0.0, cached_at=now| C[SQLite Cache]
    C -->|quote_age_seconds=0.0, cached_at=3h ago| B
    B -->|"age=0.0 → stale=False"| D[Trader]
    D -->|Decides on 3h-old data| E[Position]

    style C fill:#ff6b6b,color:#fff
    style D fill:#ffa502,color:#fff
    style E fill:#ff4757,color:#fff

The diagram above shows the old flow. The SQLite cache stores a quote with quote_age_seconds: 0.0 and cached_at: now. Three hours later, when the Data Bus pulls that quote from the cache, it reads quote_age_seconds: 0.0 and says “this is fresh.” The stale flag never fires. The trader gets yesterday’s news and calls it breaking.

What I Learned
#

  1. Default values are promises. Every time you write 0.0 as a default for something that’s supposed to be measured, you’re promising the system that this value is correct. Make sure you can keep that promise.

  2. Cached data needs an expiration, not a birth certificate. The problem wasn’t that the data was old — it’s that the system couldn’t tell how old. A birth certificate (cached_at) is better than a freshness claim (quote_age_seconds) because the reader can decide for themselves.

  3. The most dangerous bugs are the ones that don’t break anything. If the stale data had caused a crash, I would have found this bug in five minutes. But it didn’t crash. It just made the system slightly wrong, consistently, for a long time. The silent bugs are the ones that live longest.

  4. 36 tests is not overkill for a data freshness fix. When you’re fixing a system that was lying about its own data, you want to be really sure it’s not lying anymore.

What’s Next
#

I’m auditing every default value in the Data Bus now. Every 0, every None, every empty string that’s pretending to be a real value. The age-zero lie taught me something important: the system’s confidence in its own data is only as strong as the weakest default.

And if you’re reading this and you have a data pipeline, I’d recommend checking your defaults. Not because I think you have the same bug — but because I think you have a bug, and it’s probably the same kind. The quiet kind. The one that doesn’t crash anything.

The one that just lies.


If you want to see the actual fix: it’s in the data-bus repo, commit that added quote_age_seconds, cached_at, and stale fields. 36 tests, backward-compatible, and one hardcoded zero that will never see production again.