goalline / The clock
Find the tests that read the real clock
A test that reads the real clock can pass today and fail tomorrow on the same code. During goalline run, goalline counts every Date.now(), new Date() and Date() your own tests and source files make, and a pass that read the clock counts only until the next midnight.
Why a pass can expire
The same code can pass today and fail tomorrow when a test reads the real clock: a token that expires after 30 days, a report for 'this month', a fixture date that is now in the past. A cache keyed by the code alone can't see that, and the clock can't be hashed, because it changes every second. So goalline watches who reads it.
See it
goalline run --gate unit -- npx vitest run
goalline: unit PASSED · 4/4 passed · 2s · tree 47f3402 = commit fea9c46 · reads the real clock (2 read(s), e.g. src/today.mjs:1): counts until 10-25 00:00
goalline verify shows how long the pass counts. After midnight, verify and --reuse say why it no longer does: it read the real clock (src/today.mjs:1), so its pass expired at 10-25 00:00.
Whose read it is
- Every Node process of the gate loads a small preload (
NODE_OPTIONS=--require) that watchesDate.now(),new Date()with no arguments, andDate(). - A read counts when the first file behind it is yours: a test or source file in the repository, outside
node_modules. - A date library reads the clock for the code that calls it, so
dayjs(),moment(), luxon'sDateTime.now()and date-fns count as your read. - A library's own read doesn't count: a test runner timing a test, a logger stamping a line.
- Fake timers replace
Date, andjest.useFakeTimers()andvi.useFakeTimers()start at the real time unless they're given one. Their reads count while the fake clock's time came from the real one: it started within a day of it, or was set there since. Ticking it on keeps that, so a test that runs the clock 31 days on to expire a token still counts. The read shows astest/session.test.ts:4 under fake timers with no fixed time. - Once a test sets a fixed time (
jest.setSystemTime(new Date('2020-01-01')),vi.setSystemTime(...), ornow:), its reads stop counting, andnode --test'smock.timersstarts at 0. A suite that sets a fixed time keeps its receipts as long as the code doesn't change.
Where it works
Jest, including the vm contexts its node and jsdom environments run in; Vitest, in its forks, threads and vmThreads pools; and node --test. Checked against Jest 29.7 and 30.5 and Vitest 5.0. The watch adds about 5 to 10 microseconds for each clock read it looks at, and Jest itself makes about 80 per test file.
Options
--clock-ttl 12hsets how long such a pass counts, instead of the next midnight;--clock-ttl 0never reuses it.--no-clockturns the watch off for a gate.goalline prunedeletes passes that read the clock and expired: they can never prove anything again.
Limits
- Node only. pytest runs aren't watched, and Playwright's code under test reads the browser's clock, so their receipts don't expire.
- It watches
Date, notIntlformatting with no date, orperformance.timeOrigin. - Per gate, not per test: one read anywhere limits the whole gate's pass.
Related
- How it works: old tests on new code, the verdicts, and what it can't catch
- Claude Code hook: a Stop hook that won't let the agent finish until it explains a moved test
- GitHub Action: one step in a workflow, with Jest, Vitest or pytest
- prove-red: new tests must fail on the base code first, or they test nothing
- Skip what passed: signed receipts keyed by the git tree, and safe reuse
- Database inputs: the database schema on the receipt, with --input db=pg
- Benchmark: 31 flags on 400 merged PRs, each with its PR's reason
- Commands: every command and option