Skip to content

Spell a UTC commit time one way, whichever git produced it - #662

Open
MongLong0214 wants to merge 5 commits into
mainfrom
fix-650-committedat
Open

Spell a UTC commit time one way, whichever git produced it#662
MongLong0214 wants to merge 5 commits into
mainfrom
fix-650-committedat

Conversation

@MongLong0214

Copy link
Copy Markdown
Owner

Closes #650.

The defect

committedAt is a documented field of the --json output, and its value was a raw slice of git's %cI. Git changed how it renders a zero offset, so one commit object reads two ways:

$ GIT_COMMITTER_DATE="2026-02-01T00:00:00Z" git commit --allow-empty -m x

git 2.50.1 => 2026-02-01T00:00:00Z
git 2.39.5 => 2026-02-01T00:00:00+00:00

git >= 2.9 is the floor this product declares (runtime-git-trailers.ts:25), and 2.39.5 is Debian stable's git. A consumer comparing strings, matching a pattern, or feeding a strict parser gets different answers about one repository depending on the machine that answered.

The fix

Canonicalised where the value is produced — all three places, so the scan path and both index readers cannot drift apart from each other while agreeing with git.

Only the UTC spelling is touched. A real offset says where the commit was made and is left exactly as git wrote it.

Why not at the JSON boundary

The index stores the same field. Canonicalising only on the way out would leave the two routes disagreeing with each other rather than with git — trading one inconsistency for a worse one.

Negative control

Making the function return its input reproduces the failure; restoring it passes. 224 cases across git, stale and query.

Ordering is untouched: %ct is the sort key and this never sees it.

Found by

Running the suite inside node:22.23.2-bookworm while diagnosing something unrelated. CI never saw it because git-matrix runs only ubuntu-latest and macos-latest, both of which carry a recent git — a matrix that exists to vary git, not varying it across the range the product claims. That half is recorded in #650 and is not fixed here.

`committedAt` is a documented field of the --json output and its value was a
raw slice of git's `%cI`. Git changed how it renders a zero offset — 2.39.5
writes `+00:00`, 2.50.1 writes `Z` — so the same commit read differently
depending on the machine. Measured on one commit object with both gits, and
`git >= 2.9` is the floor this product declares, so 2.39 is not an exotic
configuration: it is Debian stable.

A consumer comparing strings, matching a pattern, or feeding a strict parser
therefore got different answers about one repository. The value is now
canonicalised where it is produced, which is all three places: the scan path
and both index readers.

Only the UTC spelling is touched. A real offset says where the commit was made
and is left exactly as git wrote it.

Blast: module
Undo: easy
Certainty: firm
Ruled-out: normalise every timestamp to UTC | an offset carries where the commit happened, and discarding it to gain uniformity loses a fact the record is entitled to keep
Ruled-out: canonicalise at the JSON boundary only | the index stores the same field, so the two routes would then disagree with each other rather than with git
Limit: this pins the spelling, not the clock; nothing here makes two gits agree about anything else in `%cI`
Provenance: authored
Record-Id: r-committedat650
Verified: negative control — making the function return its input reproduces the failure, restoring it passes; 224 cases across git, stale and query; the ordering key `%ct` is untouched, so no comparison changes
@github-actions

github-actions Bot commented Aug 14, 2026

Copy link
Copy Markdown

CommitLore — record lint

Trailers: clean — 5 commits in origin/main..9fa1922bbfc1e7fc58807b8a91cd48d30fcf3644
Active constraints: not read — commitlore: git log --follow accepts exactly one pathspec, so renames are not followed for 13 paths; query one path at a time to follow its rename chain (13 changed paths)

Trailer violations fail this check. Active constraints are informational — they are what the repository already decided, not a verdict on this PR.

# Conflicts:
#	dist/commitlore.mjs
#	installer/canonical-artifact.json
Provenance: authored
Record-Id: r-canon650m
Verified: docker linux/amd64 npm ci + build + artifact:manifest; artifact:verify reports cb1dc4bd; 93 cases across git and doctor against the rebuilt bundle
# Conflicts:
#	dist/commitlore.mjs
#	installer/canonical-artifact.json
Provenance: authored
Record-Id: r-canon650m2
Verified: the worktree carried no uncommitted source before building, which is what made the previous artifact wrong on another branch; docker linux/amd64 npm ci + build + artifact:manifest; artifact:verify reports 30485348; 48 cases across git and doctor-snapshot
MongLong0214 added a commit that referenced this pull request Aug 14, 2026
Two tests pin that the library and the shipped binary render one report
identically, and that NO_COLOR changes nothing but colour. Since #661 they also
counted `mcp-runtime-identity`, which enumerates live processes — so under a
parallel suite it can flip between `ok` and `warn` between two runs, and the
numbered warning list gains or loses an entry with every number after it
shifting.

That is what #662 kept failing on, on Node 22 only, while passing everywhere it
was run alone. Not a defect in that branch, and not a Node difference: the
suite's own parallelism was the variable.

#664 canonicalised the totals and the runtime detail line and left this open in
its own commit — whether the numbering also shifts was a question only CI could
answer. It answered. Canonicalising the numbering too would hide it until the
next check reads the machine; leaving such a check out of a format comparison is
the statement that is actually true about what these tests measure.

The list comes from the registry rather than being written out, so a new check
cannot fall out of these comparisons without somebody choosing it.

Blast: local
Undo: easy
Certainty: firm
Ruled-out: canonicalise the numbered list as well | it covers this instance and not the class, and the class is "a check whose status depends on the machine appears in a byte comparison"
Ruled-out: refresh the snapshots | the output depended on the machine, so a refresh moves the failure to whichever environment the snapshot was not taken in
Limit: this exempts one check from two tests; the check itself is still exercised by its own regressions and by the pinned report, which runs through the seam with an empty runtime list
Provenance: authored
Record-Id: r-formatcompare661
Verified: 35 cases in doctor-snapshot; artifact:verify unchanged at b52a10c6 because no source changed; the worktree held no uncommitted source when it was built
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

committedAt in the --json schema is serialised by git, so its format changes with the user's git version

1 participant