The 5% Bug That Almost Shipped
Contents
I'm building newsagent.fyi, an internet reader, with AI assistance. I'm 34 days in, with 66,000 lines of TypeScript and more than 1,400 commits. This happened in week three.
One click on "Mark All Read" was about to create 22,000 database rows. On a busy day, Bluesky search feeds could pull in 60,000 articles, so the approach wouldn't last long.
Claude suggested a timestamp horizon. Instead of storing a row for every read article, store one timestamp that says: everything before 2pm is read. It pointed to the original Google Reader API as precedent. This was an established pattern and looked like the obvious answer.
Then I spoke to a friend who has been working with RSS for years. He pointed out that websites don't always publish to their feeds immediately. Some feeds update daily or hourly, so an article dated 10am might not arrive until 2pm.
That breaks a horizon based on the article's publication date:
User marks all read at 1pm.
Horizon is set to 1pm.
Feed syncs at 2pm and pulls in an article dated 10am.
The article falls behind the horizon and appears read.
The user never sees it.
The standard solution had a bug. My friend suggested tracking batches by insertion order, which would be deterministic but more complicated. I asked Claude to compare the two approaches.
The trade-off
Claude came back with this table:
| Timestamps | Batches | |
|---|---|---|
| Simple | Yes | No |
| Deterministic | No | Yes |
| Query complexity | 2 JOINs | 3 JOINs |
| Data integrity | Risk | Solid |
The timestamp was simple but risky; batches protected the data at the cost of another table and more complex queries. Claude tried a workaround using the latest publishedAt value from the visible articles, but that still failed when articles arrived out of order within the same sync.
Before rewriting the query layer around batches, I asked it to measure the problem against the production database. Across 28,023 articles from 98 feeds, 4.99% arrived after newer articles from the same feed. A publication-time horizon would have marked those articles as read before the user had seen them.
| Feed Type | Affected Articles |
|---|---|
| Google News searches | 625 |
| Hacker News | 356 |
| BBC/NYT News | 297 |
| Standard RSS | 73 |
Five percent is too high for a core feature. But batch tracking meant new tables, flipped logic and permanent maintenance overhead. I asked Claude to keep looking for a smaller change.
The timestamp we control
Every article already had two timestamps. publishedAt came from the feed and could be late or out of order. createdAt recorded when newsagent ingested the article. We controlled the latter, which gave us the ordering property we needed.
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.
This kept the simplicity of the original proposal without relying on a timestamp supplied by somebody else.
I then worked through the awkward cases. An explicit "mark unread" state takes priority over the horizon:
CASE
WHEN explicit_state = false THEN false -- override wins
WHEN createdAt < horizon THEN true
ELSE false
ENDIf someone marks all read, scrolls back, marks one article unread and then marks all read again, the explicit unread row still wins. The cases we could think of held, leaving a small schema:
CREATE TABLE read_horizons (
user_id TEXT,
feed_id UUID,
created_before TIMESTAMP,
UNIQUE(user_id, feed_id)
);The code shipped that afternoon. The conversation took about an hour and the fix took ten minutes.
What caught the bug
Claude knew the established pattern and could test it quickly against real data. My friend knew how badly behaved RSS feeds can be. I kept pushing when the safe answer looked too expensive. Without the challenge from somebody who knew the domain, the first answer might have shipped. Without Claude, I would probably have reached for the batch design and accepted the extra complexity.
The useful part of the collaboration was the disagreement. "Are you sure?" turned a plausible answer into one we had actually tested.