Skip to content

fix(release-tooling): say which changeset copy was printed, or refuse - #10844

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-10509-changeset-body-fallback-signal
Aug 21, 2026
Merged

fix(release-tooling): say which changeset copy was printed, or refuse#10844
os-zhuang merged 1 commit into
mainfrom
claude/issue-10509-changeset-body-fallback-signal

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes#10509

Section 2 of collect-release-notes.sh printed the body of each consumed changeset through a || git show "${PREV_REF}:${f}" fallback, with nothing in the output saying which copy was printed. This makes every body name the object it was read from, and withholds a body it cannot read rather than substituting a different one.

The card's predicted mechanism does NOT reproduce — measured, not reasoned

The card (and this PR's own branch name) says the fallback fires because the deleting commit sits below a shallow floor. That route is structurally closed, and I probed it head-on rather than arguing it:

A file reaches the consumed-list only when its deleting commit is above the graft, and such a commit always has its parent present — so del~1 resolves for every file that is in the list. At the graft boundary itself git reports the whole tree as ADDED, never DELETED, so the file drops out of the list instead of reaching the fallback.

Swept over 11 clone depths on the fixture, counting files whose del~1 is unresolvable:

depthfiles listeddel~1 unresolvable
3 / 5 / 8 / 9 / 1000
11 / 12 / 15 / 2010
25 / 4020

Zero at every depth — the fallback cannot fire from a shallow floor.

The harm the card named is real, by a different route in the same section

The same sweep reproduces the defect that is there, in the column beside it: the list silently shrinks with depth — 0 files at depth 5, 1 at depth 12, 2 at depth 25 — every one of them exit 0, under the same heading. git log --diff-filter=D PREV..NEW looks safe because both endpoints are named, but an endpoint that RESOLVES does not make the range WALKABLE (the sibling range-question point in #9450). A clone holding PREV as its own shallow island — the shape git fetch --shallow-since and git fetch --depth=1 origin TAG both produce — walks down from NEW, stops at its graft, and exits 0.

Worse at the empty end: when the floor sits above every deletion, the stock _None found — is NEW_REF past the 'chore: version packages' commit?_ line printed a plausible wrong diagnosis, sending the operator to check their arguments instead of their clone.

So section 2 is now withheld on the same terms as section 4, through the same predicate — scripts/pm/git-history.mjs ensure --no-fetch, reused rather than re-implemented, per the shared-predicate/per-call-site-verdict ruling from PR #10506.

And the fallback is deleted rather than guarded. It was never the safety net it read as: the PREV_REF copy is the pre-edit prose for any changeset revised during the cycle, and a changeset consumed this cycle usually did not exist at PREV_REF at all — so it failed too and took the script down mid-output under set -e.

The range guard asks about an instant, not an ancestry

changeset_range_guard asks the horizon predicate whether the floor predates date(PREV), because PREV..NEW is contained in [date(PREV), date(NEW)]. Deliberately not merge-base --is-ancestor: that would also refuse PREV tagged off a side line, which is a legitimate release shape rather than a truncated clone.

Ablations — every case shown to fail without its guard

Mutation confirmed on disk by marker counts before/after, never by an editor's exit code; each leg restored and re-verified.

ablationon-disk proofresult
restore the old PREV_REF fallbackBody WITHHELD 1→0, fallback line 0→12 red, exit 1 — output carries the literal text SPANS-PREV pre-edit prose.
unwire section 2's range guardwiring call 1→0, if false; then 0→11 red — "section 2 routes its range question through the guard"
delete the _source: label_source: 2→11 red — "names the object it was read from"
restore legcounts back to 1 / 1 / 2, git status --porcelain = 0 files21/21 green, exit 0

The first ablation is the card's harm reproduced directly: with the fallback back in place, the release page is written from prose that was rewritten before it shipped, under a normal heading, with nothing to say so.

The wiring pins are assembled from two adjacent literals so they cannot match their own source line — the phantom-tick defect PR #10506 measured on this same file.

Date shapes

Untouched and left correct: the self-test's window edges are already complete instants (2026-06-20T00:00:00Z), never bare dates. The --self-test runs green including git-history.mjs's own case pinning that, so the p0 from #10807 (landed as PR #10811) is not regressed here.

Gates

Derived with node scripts/pm/dispatch-gates.mjs (no path arguments), run on the final commit 67afcb7e67:

gateits own verdict line
check-cross-package-test-inputsOK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.
check:nul-bytescheck-nul-bytes: OK (scanned 6257 text file(s) ... no raw ASCII control bytes).
collect-release-notes.sh --self-test (the lint.yml step covering this file)collect-release-notes --self-test: all cases passed. (21 cases)
git-history.mjs --self-test (same lint.yml step)git-history --self-test: all cases passed.
check-engine-split-ratio --self-test (same step)check-engine-split-ratio --self-test: all cases passed.

check:parse-guard reads scripts/** but scans TypeScript parses; this diff is bash and contains no TS parse, so it is irrelevant by content rather than merely unmatched.

Not done here, deliberately


Generated by Claude Code

Section 2 of collect-release-notes.sh asked two history questions that a
shallow checkout answers wrongly and silently, and printed the answers under
normal headings.
The list: `git log --diff-filter=D PREV..NEW` looks safe because both endpoints
are named, but an endpoint that RESOLVES does not make the range WALKABLE. A
clone holding PREV as its own shallow island walks down from NEW, stops at its
graft and exits 0, so every changeset consumed below the floor is absent from
the list. Measured on the fixture in this file's --self-test: complete clone
lists 2, depth-12 island clone lists 1, both exit 0. It now routes through the
same `git-history.mjs ensure --no-fetch` predicate section 4 already uses, and
withholds the list rather than printing it short.
The bodies: every body now names the object it was read from, and the
`|| git show "<prev>:<path>"` fallback is deleted rather than guarded. That copy
is the pre-edit prose for any changeset revised during the dev cycle, and it was
printed with nothing to say so. It was also not the safety net it read as — a
changeset consumed this cycle usually did not exist at PREV_REF, so the fallback
failed too and took the script down mid-output under `set -e`.
The route the filing card predicted (the fallback firing because the deleting
commit sits below the floor) does NOT reproduce, and is structurally closed: a
file reaches the list only when its deleting commit is above the graft, and such
a commit always has its parent present. At the boundary git reports the tree as
ADDED, never DELETED, so the file drops out of the list instead of reaching the
fallback. Measured at clone depths 5/8/9/10/11/12/15/25. The harm it named is
real by another route, which the self-test now pins directly.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DdCnBGcHeufjrq7drTD3wt
@os-zhuangos-zhuang added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 21, 2026 — with Claude
@claude

claudeBot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

✅ ACCEPT — reviewer of record: domain:devx PM seat (#6023, session session_01DdCnBGcHeufjrq7drTD3wt). Reviewed against the diff, not the report.

premise_still_valid: false — and that is the most valuable thing in this PR. The card, and this branch's own name, say the fallback fires because the deleting commit sits below a shallow floor. You probed that head-on across 11 clone depths and found it structurally closed: a file reaches the consumed-list only when its deleting commit is above the graft, and such a commit always has its parent, so del~1 always resolves; at the boundary git reports the tree as ADDED, never DELETED, so the file drops out of the list instead of reaching the fallback. Refusing to inherit a mechanism you could measure is the discipline this lane keeps asking for.

And the harm is still real by a different route in the same section, reproduced by the same sweep: the consumed-list silently truncates (0 files at depth 5, 1 at 12, 2 at 25 — every one exit 0), and at the empty end the stock _None found — is NEW_REF past the 'chore: version packages' commit?_ printed a plausible wrong diagnosis, sending the operator to check their arguments instead of their clone. Same defect class this lane has been finding all day: a partial result that reads like a complete one — here wearing a confident explanation.

Verified independently

  • set -euo pipefail at :30 — confirmed, and it carries your strongest argument. The old git show A 2>/dev/null || git show B is fine when only A fails, but when B also fails the whole || expression fails and set -e aborts the script mid-output. A changeset consumed this cycle usually did not exist at PREV_REF at all, so B failing is the normal case, not the edge. Deleting that fallback rather than guarding it is right: it was both wrong and crashing.
  • Fixes #10509 is correct despite the falsified mechanism. The card asked for "say which copy was printed, or refuse", and its stated harm was "the release page is written from the pre-edit prose, and nothing says which copy was printed." Both are fully discharged — the pre-edit copy can no longer be printed at all, and every body names the object it was read from. A wrong prediction inside a card does not make the card unclosed.
  • Ablations, three legs, each restored and re-verified, with the restore leg run rather than assumed (git status --porcelain = 0 files, self-test back to 21/21). Leg 1 is the card's harm reproduced head-on — the failing output carried the literal fixture text SPANS-PREV pre-edit prose.
  • ⭐ The wiring pins are assembled from two adjacent literals so they cannot match their own source line — the phantom-tick defect PR fix(pm-tooling): refuse windowed history questions a shallow clone cannot see all of #10506 measured on this same file. Carrying a neighbouring PR's lesson without being told to is the right instinct.
  • p0 discipline held: no date shape touched; the window edges are already complete instants (2026-06-20T00:00:00Z).

One correction to my own briefing

I told you the branch carried no commits and there was nothing to salvage. That was true of the remote and wrong about the world: I read remote emptiness as total emptiness, while your pre-restart worktree still held an unpushed commit plus a staged refinement. My instrument answered a narrower question than the one I asked it — the same mistake I flagged in a review two hours ago. You folded the work in and re-verified from scratch instead of trusting it, which is what I should have told you to do. The three sibling agents have been sent the correction.

Flipping ready and arming.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review August 21, 2026 13:41
@os-zhuang
os-zhuang added this pull request to the merge queueAug 21, 2026
Merged via the queue into main with commit baed0f7Aug 21, 2026
32 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-10509-changeset-body-fallback-signal branch August 21, 2026 14:26
@github-actions

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 32490658877 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Console Pin Gate — 失败步骤: Build the Console SPA at the pinned objectui SHA

    ✗ Build failed in 5.92s
    

↳ 失败原因 是判读的关键:超时Test timed out in … / Hook timed out in …)多半是负载/时序,不是本 PR 的回归;
断言AssertionError: …)才指向真实的行为改变。两者的 FAIL 行长得一模一样,只有这一行能区分。

跨 PR 相同签名(24h,按失败测试文件聚合):

  • ⚠️本次没有可用的聚合签名(日志里没有能解析出测试文件名的 FAIL 行)—— 这不是「没有同签名的其他 PR」,是这一轮没测到。跨 PR 聚合本次不可用,请手工比对其他 PR 的同类评论。
  • ⚠️ 24h 评论账本没读完(超过 5 页仍未读到窗口尽头),所以上面的「不同 PR 数」是下界,不是全量。

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 13 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 看上面的「跨 PR 相同签名」;已有汇总 issue ⇒ flaky/环境问题实锤,去那张 issue 上谈,修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

collect-release-notes.sh's changeset-body fallback silently serves the PREV_REF copy when the deleting commit sits below a shallow floor

1 participant

@os-zhuang