Skip to content

The 5% Bug That Almost Shipped

#dev#ai#collaboration

I'm building an RSS reader with Claude Code. 34 days in, 66,000 lines of TypeScript, 1,400+ commits. This is a story from week three.


COLD OPEN

INTERIOR — HOME OFFICE — DAY

A developer stares at a database query. The numbers don't lie.

Twenty-two thousand rows. One click of "Mark All Read."

That's twenty-two thousand INSERT statements. On a busy day, with Bluesky search feeds pulling sixty thousand articles, the math gets ugly fast.

There has to be a better way.


ACT I: THE CONFIDENT SOLUTION

The AI has done its homework.

"Timestamp horizons," it says. "Industry standard. Google Reader used this exact pattern."

The proposal is elegant:

Instead of creating 22,000 rows...
Store one timestamp: "Everything before January 14th, 2pm is read."
Single row. O(1) operation. Problem solved.

The AI cites sources. Notification systems at scale. The original Google Reader API documentation. This is a known pattern. High confidence.

The developer nods. Makes sense. Ship it.

But then a Slack message arrives.


ACT II: THE SKEPTIC

A friend who's been working with RSS since before most developers knew what it stood for has thoughts:

"Websites may not publish to their RSS feeds immediately. Some sites update them daily or hourly. An article dated 10am might not appear in the feed until 2pm."

The developer reads it twice.

Then types the scenario into Claude:

User marks all read at 1pm. Horizon is set to 1pm. Feed syncs at 2pm, pulls in article dated 10am. 10am is less than 1pm. Article marked read. User never saw it.

A bug. Hiding in plain sight. In the "industry standard" solution.

The friend suggests an alternative: batch-based tracking. Assign articles to batches by insertion order. Mark batches as read. More complex, but deterministic.

The developer pushes both ideas to Claude.

"Compare them. Rigorously. Data integrity is the goal."


ACT III: THE STALEMATE

The AI comes back with a comparison table:

TimestampsBatches
SimpleYesNo
DeterministicNoYes
Query complexity2 JOINs3 JOINs
Data integrityRiskSolid

Classic engineering tradeoff. Simple but risky. Robust but complex.

The AI proposes a workaround: instead of setting the horizon to NOW(), use MAX(publishedAt) from visible articles. Only mark what actually exists.

"Still feels flaky," the developer types. "What about articles arriving out of order within the same sync?"

Silence. The workaround has holes.


ACT IV: THE DATA

"Query the real database," the developer says. "How bad is this actually?"

The AI runs analysis scripts against production. 28,023 articles. 98 feeds.

The results come back:

4.99% slip-through risk.

Not theoretical. Not edge cases. Five percent of articles arrive after newer articles from the same feed. These would be incorrectly marked as read.

Feed TypeAffected Articles
Google News searches625
Hacker News356
BBC/NYT News297
Standard RSS73

News aggregators. Social feeds. Sites that republish or update content.

"Five percent," the developer mutters. On a core feature. Unacceptable.

But the batch approach means rewriting the entire query layer. New tables. Flip logic. Maintenance overhead forever.

There has to be another way.


ACT V: THE BREAKTHROUGH

The AI is quiet for a moment. Then:

"The batch-based approach gives determinism by using insertion order instead of publish time. But what if we use a timestamp we control?"

The developer leans forward.

"There are two timestamps on every article:

publishedAt — when the author dated it. External. Unreliable.

createdAt — when we ingested it. Ours. Deterministic."

The insight clicks.

User marks all read at 1pm.
Horizon: createdAt < 1pm = read.

Article arrives at 2pm (dated 10am in the original).
Its createdAt is 2pm.
2pm > 1pm.
Shows as unread.

Same simplicity as the original proposal. Same data integrity as the batch approach.

The developer types: "So NOW() is safe to use?"

"Yes. Articles arriving later will always have a later createdAt. Zero slip-through."


ACT VI: THE VALIDATION

The friend's voice echoes in the developer's head. Trust, but verify.

"What about mark-unread after the horizon passes?"

CASE
  WHEN explicit_state = false THEN false  -- override wins
  WHEN createdAt < horizon THEN true
  ELSE false
END

"Explicit overrides take precedence. Works as expected."

"What if someone marks all read, scrolls back, marks something unread, then marks all read again?"

"New horizon overwrites. Explicit unread row still exists. Still shows unread. Correct."

The developer runs through every scenario the friend might ask about. Each one holds.


EPILOGUE

The final schema:

CREATE TABLE read_horizons (
  user_id TEXT,
  feed_id UUID,
  created_before TIMESTAMP,
  UNIQUE(user_id, feed_id)
);

One small table. One timestamp comparison. Zero slip-through.

The code ships that afternoon. The developer pushes with confidence—not because the AI said so, but because the solution survived a skeptic's scrutiny, real-world data analysis, and systematic validation.

Three minds. One solution. None of them could have reached it alone.


THE LESSON

The AI knew the industry pattern. Cited the sources. High confidence.

It would have shipped a 5% bug.

The developer knew RSS feeds are chaotic. Pushed back. Demanded data.

Would have over-engineered with batch tracking.

The breakthrough came from combining their knowledge:

  • AI: "Here's the standard approach."
  • Developer: "That won't work in the real world."
  • AI: "Here's the complex alternative."
  • Developer: "Too much overhead. There must be another way."
  • AI: "What if we use a different timestamp?"

The insight—using createdAt instead of publishedAt—was hiding in plain sight. It took a skeptic to reject the first answer and a synthesizer to reframe the problem.

That's the future of software development. Not AI replacing humans. Not humans ignoring AI. A conversation where each party contributes what they're good at:

  • AI: Breadth. Speed. Pattern recognition. Research synthesis.
  • Developer: Domain expertise. Skepticism. "That won't work here."

The 5% bug never shipped.

Because someone asked "are you sure?" and the AI said "let me check."


This bug was caught while building newsagent.fyi, an RSS reader I'm building with Claude Code. The conversation took about an hour. The fix took ten minutes.


Appendix: The Conversation Flow

For the technically curious:

  1. AI proposes timestamp horizons (standard pattern)
  2. Developer asks about batch alternative (friend's suggestion)
  3. Friend's feedback arrives: "RSS feeds delay publishing"
  4. AI proposes MAX(publishedAt) workaround
  5. Developer pushes: "Still flaky. Compare rigorously."
  6. AI delivers full comparison, admits stalemate
  7. Developer demands real data analysis
  8. AI finds 5% slip-through rate in production
  9. AI reframes: "What makes batches deterministic?"
  10. Breakthrough: Use createdAt instead of publishedAt
  11. Developer validates with edge case questions
  12. Solution confirmed. Code ships.

The AI didn't get it right the first time. The developer didn't have the bandwidth to research alternatives. Together, they found something neither would have reached alone.