Skip to content

fix(pm): give git-history self-test windows a complete instant, not an approxidated bare date - #10811

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-10807-git-history-selftest-approxidate
Aug 21, 2026
Merged

fix(pm): give git-history self-test windows a complete instant, not an approxidated bare date#10811
os-zhuang merged 1 commit into
mainfrom
claude/issue-10807-git-history-selftest-approxidate

Conversation

@claude

@claudeclaudeBot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Fixes#10807

scripts/pm/git-history.mjs --self-test has been red on main every day after
12:00 UTC, failing Lint & Repo Gates for every PR in the repo and getting
queued PRs evicted on rebuild. It is not a code regression.

Root cause, re-derived here

git rev-list --since=2026-06-20 is an approxidate: git fills the missing
time of day from the current wall clock, not from midnight. The self-test's
fixture stamps its 40 commits at 12:00:00Z, one per day, and asks for
--since=2026-06-20 --until=2026-07-11 expecting 21. So the --since edge
sweeps across c19 once a day.

Reproduced standalone at 12:09:40Z with a fresh fixture of identical stamps and
the identical window:

$ git rev-list --count --first-parent --since=2026-06-20 --until=2026-07-11 main
20
$ git rev-list --first-parent --since=2026-06-20 --until=2026-07-11 --format='%s %cI' main | tail -1
c20 2026-06-21T12:00:00+00:00 <- oldest in window
$ git log --format='%s %cI' main | grep '^c19 '
c19 2026-06-20T12:00:00+00:00 <- the dropped boundary commit

c19 is the dropped commit, and it is exactly the --since boundary. All three
failures are the same off-by-one: the wide window answers 20 instead of 21
(twice), and the narrow --since=2026-07-08 window answers 2 instead of 3
because it collides with c37 the same way.

The fix

Every window edge in the self-test is now a complete UTC instant
(2026-06-20T00:00:00Z), which git parses exactly and never approxidates. The
edges are named constants, used by the CLI invocations, the raw-git baseline and
the historyHorizon readings alike, so a window is spelled in exactly one place.

Placing each edge at 00:00:00Z also puts it 12 h from the nearest commit
stamp
— half the fixture's daily cadence, the widest gap available. A new case
recomputes both properties (complete-instant shape, and edge-to-stamp distance)
from the constants rather than asserting about them, so re-cadencing the fixture
or moving a window re-runs the check instead of dating it.

Why not the alternatives. Moving the fixture stamps off 12:00 relocates the
collision instead of removing it, and leaves the edges approxidated — midnight
stamps are one timezone away from the same bug. Choosing boundaries that merely
sit between stamps helps only while the edges stay bare: an approxidated edge
still moves with the clock, so the property would hold by luck rather than by
construction. The chosen fix removes the wall clock from the computation
outright, and then also takes the maximum-separation placement. Note that
collect-release-notes.sh --self-test, which runs over an identical 40-commit
fixture in the same lint.yml step, has always spelled its window this way and
is green right now — this change makes the two consistent.

No assertion was loosened: the counts stay exact (21 / 3 / 5). The tool's
behaviour is unchanged
resolveSince hands --since to git verbatim and
Date.parse only validates it, so the complete instant survives both; the defect
was in the question the test asked, not in the tool.

Proof it is not just "green right now"

A green run proves nothing here — the bug produces exactly that evidence for
twelve hours a day. Clock-faking was available: stock git reads
GIT_TEST_DATE_NOW (epoch seconds) as "now" for approxidate resolution, which is
precisely the mechanism under test. The self-test's git subprocesses inherit it.

faked nowpre-fix (origin/main)this branch
2026-08-21T00:30:00Zexit 0 all passedexit 0 all passed
2026-08-21T06:00:00Zexit 0 all passedexit 0 all passed
2026-08-21T11:59:59Zexit 0 all passedexit 0 all passed
2026-08-21T12:00:01Zexit 1 — 3 FAILEDexit 0 all passed
2026-08-21T12:30:00Zexit 1 — 3 FAILEDexit 0 all passed
2026-08-21T18:00:00Zexit 1 — 3 FAILEDexit 0 all passed
2026-08-21T23:59:59Zexit 1 — 3 FAILEDexit 0 all passed

The pre-fix column flips at exactly 12:00 UTC, reproducing the transition CI
pinned between 11:57:14Z (success) and 12:02:35Z (failure). That flip is what
makes the fixed column meaningful: it proves the faked clock really reaches the
git processes inside the self-test, so the all-day green is a measurement rather
than a no-op.

Ablation. Reverted the fix with git checkout origin/main -- scripts/pm/git-history.mjs,
confirmed on disk by anchor count (bare --since=2026-06-20', '--until back to 4
occurrences, WINDOW_SINCE to 0) rather than by an editor's exit code; the
failure returned at the red time of day exactly as tabulated. Restored from the
branch and confirmed byte-identical: blob 4520ae27c0ff49163525b0f49616e46209a39207
before and after, git status --porcelain empty.

Gates, all at 33f00f8287

node scripts/pm/git-history.mjs --self-test -> EXIT=0, 37 cases passed, 0 failed
(36 pre-existing + 1 new guard), run at 12:14:33Z
— inside the previously-red window
node scripts/pm/dispatch-gates.mjs -> EXIT=0, derived 1 changed path
node scripts/check-entry-guard.mjs -> selftest EXIT=0 (47 cases), gate EXIT=0
node scripts/check-parse-guard.mjs -> selftest EXIT=0, gate EXIT=0
node scripts/check-cross-package-test-inputs.mjs -> selftest EXIT=0 (90 cases), gate EXIT=0
node scripts/check-nul-bytes.mjs -> selftest EXIT=0 (75 assertions), gate EXIT=0

Exit codes captured with cmd > file 2>&1; ec=$?, never read through a pipe.
entry-guard and parse-guard were run by hand because dispatch-gates is
blind to them — it reports check:parse-guard as unreachable by construction
(its declared population scripts is refused as too generic for want of a path
separator), and entry-guard does not appear at all.

No changeset: gate tooling publishes nothing. skip-changeset applied additively.

Swept for other instances of the same defect class

Checked whether anything else in the repo hands a bare YYYY-MM-DD to git.
Nothing else is affected, and nothing else is currently red:

  • scripts/collect-release-notes.sh — identical 40-commit 12:00Z fixture, but
    already spells its window 2026-06-20T00:00:00Z / 2026-07-11T00:00:00Z.
    Ran it at 12:12:31Z: exit 0, all cases passed.
  • scripts/check-engine-split-ratio.mjs — relative windows (<N> days ago) with
    the fixture re-dated to end today and ≥1 day of slack at both edges, so no edge
    sits knife-edge on a stamp. Ran it at 12:12:45Z: exit 0, all cases passed.
  • scripts/pm/check-governed-merges.mjs and check-engine-split-ratio.mjs's
    production paths build their --since with new Date(...).toISOString(), which
    is already a complete instant.

Worth noting: both of those self-tests run in the same lint.yml step, after
git-history.mjs. Because the step is a single run: block under bash -e, the
first failure aborts it — so neither has actually executed in CI since 12:00Z
today. They are green; they were merely masked.

Generated by Claude Code


Generated by Claude Code

… a bare date
`git rev-list --since=2026-06-20` is an approxidate: git fills the missing
time of day from the CURRENT wall clock, not from midnight. The self-test's
fixture stamps its 40 commits at 12:00:00Z, so the `--since` edge swept
across c19 (2026-06-20T12:00:00Z) once a day — 21 commits before 12:00 UTC,
20 after. The self-test was therefore green every morning and red every
afternoon, failing `Lint & Repo Gates` for every PR in the repo and getting
queued PRs evicted on rebuild.
Every window edge in the self-test is now a complete UTC instant
(`2026-06-20T00:00:00Z`), which git parses exactly and never approxidates.
Placing each edge at 00:00:00Z also leaves 12 h — half the fixture's daily
cadence, the widest gap available — between it and the nearest commit stamp.
A new case recomputes both properties from the window constants, so the
defect class cannot return silently.
The tool's own `--since` handling is unchanged; only the question the test
asks is. Counts stay exact (21 / 3 / 5) — no assertion was loosened.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 21, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 21, 2026 12:20
@os-zhuang
os-zhuang enabled auto-merge August 21, 2026 12:20
@os-zhuang
os-zhuang added this pull request to the merge queueAug 21, 2026
Merged via the queue into main with commit 9ca303eAug 21, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-10807-git-history-selftest-approxidate branch August 21, 2026 12:41
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

1 participant

@os-zhuang