Skip to content

bump-objectui.sh's walkability guard cannot detect a shallow objectui clone — the digest silently ships a complete-looking release record derived from a truncated history #9408

Description

@os-zhuang

Found while correcting .changeset/console-82a94170c405.md (card #8893, PR #9398). Filing separately because the corrected table is one artifact, whereas this makes every console pin bump run in a shallow container ship the same fault.

The defect

scripts/bump-objectui.sh:139-142 decides whether the pin range can be walked:

RANGE_OK=0
if [[ "$OLD_SHA"!="(none sentinel)" ]] && git -C "$OBJECTUI_ROOT" cat-file -e "${OLD_SHA}^{commit}"2>/dev/null;then
RANGE_OK=1
fi

Its own comment names the case it means to catch — "(A shallow clone or a first-ever pin may not have OLD reachable — degrade to the tip subject, and SAY SO in the artifact: a degraded list and a complete one must never look alike, #4731.)" — but the implemented test only asks whether OLD_SHAexists as an object. It never asks whether the walk between the endpoints is complete.

In the common container shape the objectui clone is shallow and its graft boundary sits inside the pin range. Then:

  1. cat-file -e OLD_SHA passes (the object is there), so RANGE_OK=1 and the degraded path at bump-objectui.sh:163 never fires.
  2. collectAddedChangesets() runs git log --no-merges --diff-filter=A --name-only ... -- .changeset/, which walks only as far as the graft.
  3. The graft boundary commit is parentless, so git diffs it against the empty tree and reports every .changeset/*.md in its tree as Added by it.
  4. The seen dedup in collectAddedChangesets() hands it exactly the paths no newer commit claimed — a single "sink" commit absorbing a whole batch.
  5. objectui-changeset-digest.mjs exits 0. Nothing in the artifact says the history was incomplete.

Measured, not inferred

The landed .changeset/console-82a94170c405.md credits 36 of its 119 entries to 183d09b78, a commit that adds exactly one changeset file. Reproduced exactly by grafting a full clone at that commit:

$ git rev-parse 183d09b78 > .git/shallow # synthesize the boundary
$ git rev-list --count --no-merges 665661ab0932..82a94170c405
110 # the landed record's denominator
$ git cat-file -e 665661ab0932^{commit} && echo PASSES
PASSES # so RANGE_OK=1, no degradation
$ node scripts/objectui-changeset-digest.mjs \
--objectui-root GRAFTED_CLONE \
--from 665661ab0932 --to 82a94170c405 --max 100 --out repro.md
minor # exit 0
→ 121 releasing changeset(s), ..., 18 commit(s) without a changeset
$ diff (grep '^- ' repro.md) (git show origin/main:.changeset/console-82a94170c405.md | grep '^- ')
# byte-identical entry list

(the last line was a bash process substitution; the angle brackets are dropped here so the stored body survives GitHub's sanitizer)

The second data point falls out of the same mechanism: PR #9398's competing derivation reported 137 non-merge commits and pinned 12 entries to 75444e38a. 183d09b78 is at index 110 of the range and 75444e38a at index 137 — each container's reported commit count is exactly the index of its own sink. Two independent confirmations.

Ground truth on an unshallowed clone is 191 non-merge commits (all, --no-merges and --first-parent all agree; the range is linear).

Why this matters more than the one table

Suggested repair (not implemented here)

Make the guard test walk completeness rather than object presence, and keep the honest degradation that already exists:

  • refuse-or-degrade when git rev-parse --is-shallow-repository is trueand any graft boundary is an ancestor of the to endpoint / inside the range;
  • also verify the to endpoint resolves (today only OLD_SHA is checked — in the container that produced the landed record, 82a94170c405 was initially absent entirely);
  • better still, have bump-objectui.sh deepen first (git fetch --unshallow, or --shallow-since past the old pin) and only degrade if that fails, since the correct history is one fetch away;
  • consider a cheap sanity assertion in objectui-changeset-digest.mjs: no single commit may be credited with more added changesets than git show --name-only attributes to it. That check alone would have caught all three bad runs.

Related: #4731 (why the digest exists), #6175 (the inverse false signal), #6494 (the ADR-0087 placeholder).

Repro material and the corrected table are in PR #9398.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions