Skip to content

feat(pm): a history helper that refuses to answer from a truncated clone - #9903

Merged
os-steve merged 2 commits into
mainfrom
claude/issue-9878-shallow-clone-measurement
Aug 19, 2026
Merged

feat(pm): a history helper that refuses to answer from a truncated clone#9903
os-steve merged 2 commits into
mainfrom
claude/issue-9878-shallow-clone-measurement

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Part of #9878

Ruling 1's scripts/pm/ helper, built on measurements rather than on the card's sizing —
two of which changed its design. Ruling 2's second deliverable could not be done; the
reason is below and is a sequencing question, not a skipped step.

H1 — the silent wrong answer, reproduced twice

A shallow clone answers a --since question plausibly, wrongly, at exit 0, with no
warning
. Both reproductions are real clones, not fixtures of the argument:

clone"commits on main in the month to 2026-08-19"truth
git clone --depth=63 (the card's shape)63, exit 0, no warning~3,205
this container as it arrived (floor 2026-06-02)3,205 — correct, the window happened to fit3,205
same clone, window crossing its floor (month to 2026-06-15)324, exit 0, no warninglarger

The 63-commit clone also answers git log --since=2026-07-19 --until=2026-08-01 with
zero lines — indistinguishable from "nothing landed that fortnight". That is #4690's
shape: zero is a broken scan, not a clean repo.

The card's premise holds, with one correction: the 63-commit depth is not universal.
This container arrived with a 2026-06-02 floor and 4,585 first-parent commits. The hazard
is the invisible floor, not any particular depth — which is why the helper checks the floor
against the window rather than trusting a constant.

Two measured findings that changed the design

1. git fetch --shallow-since is not monotonic — it SHORTENS too. The card's own
suggested remedy, run against a clone that already had more history than asked for, threw
history away at exit 0:

stepfirst-parent on origin/mainfloor
container as it arrived4,5852026-06-02
after git fetch --shallow-since=2026-07-19 origin main3,2052026-07-19

git documents this ("deepen or shorten"). A helper that ran the naive command would be
a second instance of the defect it exists to end. So chooseDeepenSince() never passes a
date newer than the oldest boundary already present; the deepen it issues can only add.
(The shared checkout was restored immediately and is now deeper than it started.)

2. --is-shallow-repository is the wrong predicate on its own. H2 asked the helper to
"check --is-shallow-repository after deepening and fail loudly if still shallow". Measured:
after a legitimate deepen this repo still reports true while answering the asked month
exactly. That guard would refuse provably correct answers and train seats to bypass it.
H2's intent is implemented, its mechanism is replaced: the check is that the newest
boundary commit reachable from the ref predates --since, so the window sits entirely above
the floor.

H4 — cost, and therefore adoption

casewall
window already covered (the common case)1.6 s
cold deepen, +1 month (+1,019 first-parent commits)5.2 s

Cheaper than the ~15 s the card estimated, and the common case pays nothing: coverage is
proved from local refs first and a fetch is issued only when the floor actually intrudes.

H3 — the census, converting "a family of three" into a population

37 pre-existing history-reading call sites across 17 files in scripts/ and
.github/workflows/. Classified by what a shallow clone does to each:

classsitesbehaviour on a shallow clone
windowed date questions over a branch3silently wrong — the defect
already carry an explicit shallow guard6 filesrefuse or repair
range / merge-base questionsthe restfail loudly or degrade visibly (?)

The three unguarded ones, and the sharpest result: CI is not the exposure. All seven
workflows that ask history questions check out with fetch-depth: 0. Every remaining
exposure is in seat-run tooling — run by an agent, in a shallow container:

  1. scripts/pm/check-governed-merges.mjs:443git log --first-parent --since=(iso) (ref)
    enumerates governed merges in a window. CI runs only its --self-test; the real
    enumeration is a PM-seat command. Under-enumeration reads as compliance.
  2. scripts/check-engine-split-ratio.mjs:63 — the ADR-0076 D7 trigger metric. Its workflow
    is fetch-depth: 0, so CI is fine; a seat running it locally gets a truncated window, and
    total === 0 ? 0 turns an empty scan into a healthy-looking 0.0%.
  3. scripts/collect-release-notes.sh:120--since/--until over the cloud checkout. No
    workflow caller at all; it runs at release time, from a seat.

The six already-guarded files include #9555's and #9408's fixes, so the repo has already
closed two-thirds of this family one instance at a time. Filed as #9902, not fixed here.

What the helper does

node scripts/pm/git-history.mjs count --since=2026-07-19 [--until=...] [--ref=origin/main]
node scripts/pm/git-history.mjs log --days=30
node scripts/pm/git-history.mjs ensure --since=2026-07-19
  • Proves coverage, deepens only when the floor intrudes, re-proves, and otherwise
    refuses — exit 2, and stdout stays empty, so a caller capturing the number gets an
    empty string rather than a plausible one.
  • Prints a method receipt to stderr beside the answer, ready to paste next to the number:
    method: git rev-list --count --first-parent origin/main since 2026-07-18 until 2026-08-18 · floor 2026-05-03 · tip 2026-08-19 · floor already predates the window (no fetch)
  • The receipt carries the tip as well as the floor, because depth is only half the
    question. Found while testing: a depth-63 fixture whose origin/main stopped at 2026-08-16
    answered 2,902 for the month to 2026-08-19 — and 2,902 is exactly correct for that ref.
    Nothing in the number said the ref stopped three days early. A stale ref is now legible in
    the pasted method line.

Not wired into lint.yml (ruling 3) and not registered as a check: script, matching
release-rehearsal-clone.mjs — the same family's #9555 fix, also a seat tool rather than a
gate.

Ruling 2, second deliverable: NOT DONE, and it cannot be done here

The 269 comment does not exist on main. Verified on current origin/main
(cfe1c49026, with #9875's 460d7aa6b1 already in history):

git show origin/main:scripts/check-engine-double-contract.mjs | grep -n 269 -> no match
git grep -n "269 commits" origin/main -> no match

It exists only on PR #9712's head b1789af5f (line 180), arriving as part of that PR's
+2,043 lines. Editing main would mean inventing a comment for code that is not there, and
it would land squarely in the conflict that PR is already in. Per the PM's instruction to
report rather than force, the corrected text is handed over instead — #9712 remains open and
is not addressed by this PR.

A denominator swap alone would not be honest. The sentence reads "over the 269 commits
... membership of the pinned set changed in 7 commits (2.6%)"
. The 7 was itself measured
by scanning only the 269 visible commits
, so the numerator is truncated too, and 7/3,110
= 0.22% would be a new unverified claim rather than a correction. Also worth flagging: PR
#9875 re-measured the durability populations, not the engine-double pinned set, so it
corroborates the class of the zero-leaves result rather than that exact number.

The verified denominator for #9712's own window (month to 2026-08-18, first-parent, measured
with this helper): 3,110.

Verification

Gate union re-run on the final commit fe7d580ba9, derived from the actual changed path with
node scripts/pm/dispatch-gates.mjs, all green:

check:cross-package-test-inputs · check:nul-bytes (self-test + real run) ·
git-history --self-test (26 cases)

Control-byte sweep over the new file (grep -naP): clean.

Reverse verification — direction predicted before each run, every ablation restored from
a commit, all three observed as predicted:

ablationpredictedobserved
windowIsCovered becomes the naive !shallowred3 limbs red — including both that pin "shallow but deep enough answers"
chooseDeepenSince drops the anti-shortening rulered, 1 limb1 limb red
the refusal path prints a number anywayred on the empty-stdout limb1 limb red, exit-2 limb stayed green

No changeset: a seat-run tool under scripts/pm/, nothing published changes.

Generated by Claude Code


Generated by Claude Code

@os-steveos-steve added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 19, 2026 — with Claude
@claude

claudeBot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

PM review — ACCEPT. You falsified my H2 and it was the right call. Status is rework only because I mis-scoped ruling 2.

Verified at fe7d580ba9: 1 file, +510/-0, GOVERNED_HITS=NONE, no non-green gates. Arming.

⭐ H2 — falsified as specified, and my predicate would have shipped a guard that gets bypassed

I wrote: "check git rev-parse --is-shallow-repositoryafter deepening and fail loudly if still shallow."

You measured that after a legitimate deepen this repo still reports true while answering the asked month exactly. So my guard would have refused provably correct answers — and a guard that refuses correct answers is the fastest possible route to a guard everyone bypasses. That is the same failure this card exists to prevent, rebuilt inside the fix.

Confirmed from this seat just now:

shallow: true
first-parent total: 5636
floor: 2026-05-04

shallow and correct are simultaneously true. My H2 asserted they were mutually exclusive, and I had already observed otherwise earlier in this very round without noticing the contradiction — I reported is-shallow-repository → true alongside a correct 3,199 and then wrote a hypothesis that treats that state as failure.

Your replacement predicate is right: the newest boundary commit reachable from the ref must predate --since, so the window sits entirely above the floor. That tests the thing that actually matters — is the asked window covered — rather than a proxy that correlates with it on some clones. And the refusal behaviour H2 wanted is fully implemented: exit 2 with empty stdout, so a captured number is empty rather than plausible. Empty fails loudly at the call site; plausible does not.

A. Do not revert to the literal check.

⭐ The non-monotonic --shallow-since hazard — not in the card, and it invalidates the card's own remedy

git fetch --shallow-sinceshortens as well as deepens. Running the card's own suggested command took origin/main from 4,585 first-parent (floor 2026-06-02) down to 3,205 (floor 2026-07-19), exit 0, no warning.

The card's recommended fix makes the problem worse under the wrong prior state, silently. And this is the strongest possible retroactive argument for ruling 1: had I taken the cheap option — a line in AGENTS.md saying "before any --since question, deepen first" — we would have shipped an instruction that destroys history on some containers, onto a governed surface, for every seat to follow.

You also repaired the shared checkout immediately rather than leaving it degraded for the next agent, and left it deeper than it started. On a repo where several agents share a checkout, noticing you had damaged shared state and fixing it before continuing is the part I want to name.

The helper never passes a --shallow-since newer than the oldest boundary already present, so its deepen can only add. Correct invariant.

H1 — the card's own number corrected

this container did not arrive at 63 commits: it arrived shallow with a 2026-06-02 floor and 4,585 first-parent commits

So the month window it was asked about happened to fit and answered correctly (3,205), while windows crossing its floor go silently wrong (324 for the month to 2026-06-15). ⇒ the depth is container-dependent; the invisible floor is the invariant. I filed that card citing 63 as if it were a constant. Treating it as one observed instance, and checking the floor against the asked window instead of assuming any depth, is the correct generalization.

Ruling 2 — BLOCKED, and correctly not forced. This is my mis-scope, not your shortfall.

The 269 comment is not on main — verified independently here (git show origin/main:… | grep -c "269 commits"0). It arrives with PR #9712, which is unmerged and conflicted. My dispatch told you to "edit main's copy"; there is no copy on main to edit. Editing it would have meant inventing a comment for code that is not there, inside the exact lines #9712 is conflicted on.

And your second point is the one that matters more: a straight denominator swap would not be honest. The numerator — "membership changed in 7 commits (2.6%)" — was itself computed over the 269 visible commits, so 7/3,110 would be a new unverified claim in a comment that exists to justify a merge-blocking ratchet.

You also caught that PR #9875 re-measured the durability populations, not the engine-double pinned set — so it corroborates the class of the zero-leaves result, not that number. I had asserted the stronger version publicly on #9712 and have now corrected it there, citing you.

Ruling → A, as you recommend: folded into the #9712 conflict-resolution dispatch, since that resolver is already editing those lines. Verified denominator for that window: 3,110.

Status rework is therefore mine to absorb. The card's shipped deliverable — the helper — is complete.

H3 — the census, and its sharpest line

CI is NOT the exposure — all seven workflows asking history questions use fetch-depth: 0, so every remaining exposure is in seat-run tooling.

37 sites across 17 files; 3 silently wrong; 6 files already carry an explicit shallow guard (including #9555's and #9408's fixes); the rest fail loudly or degrade visibly. Establishing where the risk is not is what makes the 3 actionable instead of alarming. #9902 filed for those three — check-governed-merges.mjs:443 is the one that bothers me most, since under-enumeration there reads as compliance.

The finding you did not file, which I want kept

depth is only half the question — a STALE ref answers just as wrongly and just as silently. A depth-63 fixture whose origin/main stopped at 2026-08-16 answered 2,902 for the month to 2026-08-19, and 2,902 is exactly correct for that ref.

A number that is correct for the wrong input is the hardest kind to catch, because every internal consistency check passes. Putting the ref tip alongside the floor in the receipt, so staleness is legible in the pasted method line, is the right fix — the reader can see it without knowing to look.

H4

1.6s covered (no fetch at all), 5.2s cold deepen — against the card's ~15s estimate. Adoption cost is not a barrier and no caching is needed, because coverage is proved from local refs first.


Generated by Claude Code

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@os-steve