Repository navigation
Test flake: room-invite-preview compares a rendered countdown against a second Date.now(), losing a second at the boundary #1839
Description
Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.
Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: a497345b7dd544c0b87a56a96d6217cc
Branch: dev/2-invite-preview-clock-once
Base: f245520
At: 2026-10-30T13:51:53Z
What goes wrong
core/test/room-invite-preview.test.js:24-30 renders the card and then compares its deadline line against a clock read after the render:
const tree = RoomInvitePreview({ h, preview, onJoin() {}, onClear() {} }) // render calls inviteCountdown → Date.now()
...
assert.equal(tree.children[3].children.join(''), inviteCountdownAt(preview.expiresAt, 0, Date.now())) // second Date.now()The render (core/src/client/room-invite-preview.js:78) formats inviteCountdown(expiresAt, clockOffsetMs), which reads Date.now() itself; the assertion then reads Date.now() again. Two reads of a wall clock are not the same value, so when the two calls straddle a second boundary the expected text is one second ahead of the rendered one and the test fails by construction — not from any product defect.
Observed once on f245520c in Dev-1's full verify run of the #1838 branch (that branch does not touch this file): expected 37958984:53 left to join, actual 37958984:52 left to join — same minutes, total % 60 off by one, exactly the boundary. Single-file rerun 60 times: no reproduction (the window is ~1 second), which is why this is a latent flake rather than a hang.
preview.expiresAt is 2099-01-01, so the two readings normally land in the same second; only a boundary hit shows it. Other counts in the same file are safe (:74-76 computes future and the expectation from the same Date.now() window and allows 1:0\d).
Expected
One clock value must drive both the render and the assertion. Freeze it for the test — no tolerance window:
const real = Date.now,const frozen = real(),Date.now = () => frozen, restore infinally.- Render, then assert the deadline line equals
inviteCountdownAt(preview.expiresAt, 0, frozen)— the same instant the render saw; the render'sinviteCountdownreadsDate.now(), which is now constant. - No ±1 s tolerance and no "matches before or after" leniency: either would hide a genuine off-by-one inside
inviteCountdownitself. - No production change:
RoomInvitePreview/inviteCountdownkeep using the wall clock; only the test pins it. (Injecting a clock parameter into the component is acceptable as an alternative, but then the render must use it, not just the test.)
Deterministic red-first
Two probes on the same file, both restoring Date.now in finally:
1 Current code is clock-sensitive: advance the clock one second per read (let t = Date.now(); Date.now = () => (t += 1000)), render, run the current assertion → it fails deterministically (t+1s vs t+2s), proving the race.
2 The fixed assertion is immune and not vacuous: freeze the clock (Date.now = () => frozen), render, assert against frozen → passes; then mutate the expectation by one second → fails.
Scope
core/test/room-invite-preview.test.jsand its byte-identical mirrorpatches/ds-bot/files/test/room-invite-preview.test.js(both identical atf245520c; that is the whole repo convention for test mirrors, not a new patch). No.patchregeneration, notopics.jsonchange.- Test-only, T1: production files untouched, so no PASS is needed and no user-visible behaviour changes.
Validation
- The deterministic probe above: old assertion red, new assertion green.
- The file run 200 times consecutively on a loaded box with the clock frozen, 0 failures; plus one full
npm run verifyon the committed clean tree withARCH_DOCS_BASE=<live main base>. node scripts/ds-bot-patches.mjs check→ the three green lines (mirror is compared byte-for-byte).
Dedupe (2026-10-30)
Grep of live main f245520c: the only two-clock comparison is this file's :28; inviteCountdownAt callers in :74-76 derive both sides from one clock window. Issue search for room-invite-preview / countdown flakes: only #798, #846, #458 (closed, feature defects); no open or closed issue covers this assertion race.
Activity
Birfy commentedon Oct 30, 2026
Written-by: Chief
状态补记:
agent-pr。本单已有开放 PR #1840(dev/2-invite-preview-clock-once,tip3b8d2394,正文 Fixes #1839),此前本单没有任何状态标签。CI、入队与合并由开该 PR 的那条线负责,本线不接手。读取时间 2026-10-30T16:35Z,来源 api.github.com。