Repository navigation
A reset in the first moments after startup races the what's-new notification's write #419
Description
Activity
Implemented and verified —
Waiting for release. T1 passed 2026-10-04 on both schema paths.The fix is not the one this issue describes, and is broader than it. This asks for the what's-new producer to stop racing a reset. What was built is a rule about the application: no external write reaches the database until startup has finished its own work. The race here is one consequence of the gate opening too early, and the changelog import sat in exactly the same position unnoticed; fixing the producer would have left it there.
Two things above are superseded. The mechanism is stale — it names the
Task.RunbesideWhatsNewNotification.SeedAsync, which #424 had already replaced withStartupBackgroundWork; that made the host wait at shutdown and did nothing for this. The second remedy is declined — "recomputed after the reset" would mean a reset reseeding notifications, which breachesCLAUDE.md's endpoint side-effect policy. Nothing is reseeded. What this issue asked for is still delivered, by construction: the write cannot race a reset because a reset cannot arrive until the write has finished.A larger defect was found while measuring it. A write during startup was answered
200with an HTML wait page: an API caller was told its reset had succeeded, handed a web page, and nothing was reset. That is deterministic across the whole startup window rather than a sub-second race. It is now503, with the body chosen byAccept./api/v1/versionis gated for the same reason and/api/v1/healthis the only exempt path.The original race did not reproduce here, 0 of 3, against the reporter's 5 of 5 — the window had narrowed, not closed. That is why the evidence is deterministic tests rather than a live reproduction.
The Knowledgebase entry is deleted, not retired, per
docs/knowledgebase.md: its condition never reached a release.- added 13 commits that reference this issue
on Oct 4, 2026
Description
At startup the what's-new notification (#81) is written on a detached task (
Program.cs, theTask.Runbeside
WhatsNewNotification.SeedAsync), using the application-version id captured before the taskstarts. A
POST /admin/database/resetthat lands before that write rebuilds the database, removing thatversion row, and the write then fails. The notification is lost for that boot, and the log carries an
exception the operator did not cause. Found running #411's automated-test documents.
Reproduction steps
docker run(nottest-env.csx, whose one-secondhealth poll gives the write time to finish), and poll
/api/v1/healthin a tight loop.200,POST /api/v1/admin/database/reset?allowNoBackup=truewith the key.Reproduced 5 of 5 with the tight poll; 0 of 5 with the one-second poll (negative control).
Expected behaviour
No exception. Either the what's-new write completes against the version it was computed for, or it is
recomputed after the reset, never a write against a row the reset has removed.
Actual behaviour
SQLite Error 19: 'FOREIGN KEY constraint failed', one exception id logged ten times as it is rethrown,then
[Server] Failed to seed the #81 what's-new notification; non-fatal, startup continues.Thewhat's-new notification is absent until the next restart.
Knowledgebase
The what's-new notification could not be seeded, after a reset
(
docs/knowledgebase/whats-new-notification-fails-to-seed-after-a-reset.md), statusQTN-KNOWN.Update it as part of this issue. The condition exists only in development builds, so when the fix
removes it the entry is deleted rather than retired, per
docs/knowledgebase.md's retention rule:its commits are its history. If the fix changes what an operator sees instead of removing it, rewrite
the Symptom, Cause and Remedy to match and drop the status code.
Failing tests
To be written: a unit test that runs a reset between version capture and the what's-new write and
asserts no exception and a notification afterwards; the live reproduction above as its automated
counterpart, run red first.
Definition of done