DEV Community

Srinivas Kondepudi
Srinivas Kondepudi

Posted on

Chron passed 10,000 npm downloads on September 30th. 10,158 as of this morning

Chron

Chron passed 10,000 npm downloads on September 30th. 10,158 as of this morning, five months after the first publish on May 8th.

I build a tool whose only job is to tell the difference between this was recorded and this was verified. So before writing a milestone post, I pointed it at the milestone.

The number did not survive.

What the number is

Here it is, and you can run this yourself:

curl -s "https://api.npmjs.org/downloads/range/2026-04-01:2026-10-04/chron-mcp" \
  | python3 -c "import json,sys; print(sum(x['downloads'] for x in json.load(sys.stdin)['downloads']))"
# 10158
Enter fullscreen mode Exit fullscreen mode

That is a real figure from npm's own API. I am not disputing it. The question is a different one: what does it establish?

What the number establishes

Almost nothing about whether anyone uses Chron.

Here are the six biggest download days in its history:

2026-05-08   630
2026-07-14   531
2026-08-13   519
2026-05-12   457
2026-07-09   403
2026-05-28   369
Enter fullscreen mode Exit fullscreen mode

And here is what I published on each of those days:

2026-05-08   0.1.0, 0.1.1, 0.1.2, 0.1.3, 0.1.4, 0.1.5
2026-07-14   0.1.32, 0.1.33
2026-08-13   0.1.50, 0.1.51, 0.1.52
2026-05-12   0.1.9, 0.1.10, 0.1.11, 0.1.12
2026-07-09   0.1.30, 0.1.31
2026-05-28   0.1.18, 0.1.19, 0.1.20
Enter fullscreen mode Exit fullscreen mode

Six for six. Every record day is a day I pushed a release. The all-time peak is launch day, before anyone had heard of it.

The monthly shape says the same thing. The biggest month was the month I shipped the first version:

2026-05    2523
2026-06    1134
2026-07    2307
2026-08    2294
2026-09    1758
Enter fullscreen mode Exit fullscreen mode

Strip the release-day spikes and the median day over the last thirty days is 34.5 downloads. Forty-five of the last hundred and eighty-seven days had zero.

So: a number that goes up when I act, and sits near thirty-five when I don't.

Marking the inference

Now the part I have to be careful about, because it is the whole point of the post.

I believe most of those spikes are registry mirrors, vulnerability scanners and CI caches reacting to a new version. That belief is reasonable. It is also an inference, and npm's public API gives me no way to test it — there is no breakdown by client, no distinction between a human running npm i and a crawler indexing a tarball.

So the honest summary is not "10,000 downloads are bots". It is:

  • Established: 10,158 tarball fetches. Every peak day is a release day.
  • Inferred: much of the volume is automated.
  • Unknown: how many people installed Chron and kept it.

Three categories, and the headline number lives in the weakest one. A milestone post that printed "10,000 developers" would have moved a fact from the third row into the first by writing a sentence.

Why I care about this specific mistake

Because I have spent the last two weeks finding it in my own code, over and over, in exactly one shape:

A check confirmed something was present, and reported it as a claim that it worked.

The clearest example shipped in the release I was testing when I started writing this.

Chron now has chron restore --drill — a recovery rehearsal. It verifies a backup, reports what the backup is missing relative to your live database, and touches nothing. It ends with this line:

Drill only. The live database was not touched.
Enter fullscreen mode Exit fullscreen mode

I measured that sentence. I built a database whose write-ahead log held a committed transaction, killed the process so the log was left unrecovered, hashed all three SQLite files, ran the drill from the packed tarball, and hashed them again:

before   db=cba17c04/163840   -wal=e854fde3/12392   -shm=b7fc33da/32768
after    db=35bc3010/163840   -wal=ABSENT           -shm=ABSENT
Enter fullscreen mode Exit fullscreen mode

The main file's digest changed. Both sidecars were deleted. A -wal holds transactions that are committed but not yet folded into the main file, so for a few hundred milliseconds that drill was the single most dangerous command in the product — and it said it had done nothing.

The cause is not a careless UPDATE. Reading a write-ahead log that needs recovery requires writing: SQLite has to rebuild the log index to see what the log contains, and the last connection to close checkpoints the log into the main file and removes the sidecars. I tried PRAGMA query_only = ON. I tried libsql's readonly: true. I measured both against a killed database. Both checkpointed. Recovery is not a statement, so a statement-level guard cannot reach it.

The fix is unglamorous: copy the database and its log to a scratch path, read the copy, delete it. The live files are then provably untouched, at the cost of temporary disk.

BEFORE   db=89d729fb/163840   -wal=531a0aee/12392   -shm=7cb7830f/32768
         ! 1 of 2 live session(s) are absent from this backup
         Drill only. The live database was not touched.
AFTER    db=89d729fb/163840   -wal=531a0aee/12392   -shm=7cb7830f/32768
Enter fullscreen mode Exit fullscreen mode

Same three digests. And still reading through the log — it found the session that exists only in the log, so it is not passing by refusing to look.

The test that proved nothing

One more, because it is the same bug wearing a lab coat.

My first regression test hashed the files before and after a drill and asserted they matched. It passed. Then I restored the broken code to check the test would catch it.

It still passed.

Inside a test runner the process never exits, so the connection never closes, so the checkpoint never happens. The test was watching exactly the right place and would never have seen anything occur. It took a child process — and an assertion on the -shm digest, the one part of the damage that is visible in-process — to make it real.

A test that cannot fail is a claim, not a check. Which is the same sentence as the one about downloads.

What I would rather count

Chron is a local-first audit log: it records what your AI coding tools actually did, on your machine, in a database you own, with hash-chained records you can verify offline. Nothing leaves your disk unless you explicitly export it.

That design is the right one, and it means I cannot see usage. No telemetry, no phone-home, no install ping. The number I can see is the one npm hands me, and the number I would actually want — how many people ran a verify and got a clean chain back — is one I have deliberately made it impossible for myself to know.

I am not going to fix that by adding telemetry to an audit tool. So the honest position is that I have a download count, I know what it is worth, and I am going to say so rather than round it up into a victory.

The actual milestone

Five months. Forty-five releases. One recurring defect shape, now written on the wall where I can see it.

The operating principle for the product is Chron records, it does not certify — it hands you evidence and leaves the judgement to you. It would be a strange thing to write that on the box and then certify my own growth from a number I cannot audit.

10,158 tarball fetches. Median real day, about thirty-five. Thanks to whichever of you are the thirty-five.

Top comments (1)

Collapse
 
beusebiu profile image
Eusebiu Balan •

My app sits in the same three rows. 7,695 accounts is established. About 150 people opening it on a normal day is closer to use. How many of them would miss it if it vanished is the unknown one, and it's the one I actually care about.