You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
collect-release-notes.sh's changeset-body fallback silently serves the PREV_REF copy when the deleting commit sits below a shallow floor #10509
Found while implementing #9902 (PR #10506), which guarded section 4 of the same script. Filing rather than widening that PR: this is a different sub-class (unwindowed path history with a fallback, not a windowed date question), and it is latent rather than measured damage.
The site
scripts/collect-release-notes.sh, section 2 — printing the body of each changeset the release consumed:
git show "$(git log --diff-filter=D --pretty=%H -1 "${NEW_REF}" -- "$f")~1:$f"2>/dev/null \
|| git show "${PREV_REF}:${f}"
The inner git log -- <path> walks history with no range bound, so in a shallow clone it stops at the graft. If the commit that deleted the changeset sits below the floor, the substitution is empty, the git show "~1:..." that wraps it fails, 2>/dev/null eats the error, and the || fallback prints ${PREV_REF}:${f} instead.
Why it is worth a line
The fallback is not obviously wrong — for a changeset added before PREV_REF and never edited, the two copies are identical, which is presumably why it was written. It is wrong exactly when the changeset was edited during the dev cycle: the release page is then written from the pre-edit prose, and nothing in the output says which copy was printed. Same shape as the rest of this family — a real, plausible answer with no signal attached.
Bounded today: the file list feeding this loop comes from a PREV_REF..NEW_REF walk that is itself truncated in the same clone, so the loop is likely short before it is wrong.
Possible repair
Say which copy was printed, or refuse — the horizon predicate is already in the file after PR #10506 (cloud_window_guard calls scripts/pm/git-history.mjs), so the machinery is there. Deliberately not done in PR #10506: its subject was the windowed class, and widening a release-time script on a latent case is how a scoped PR stops being reviewable.
Related
#9902 · PR #10506 · #9450 (the range-question sibling: an endpoint that resolves does not make the range walkable)
Triage: finding + domain:devx, type Task — latent-by-the-card's-own-measure (bounded today: the file list feeding the loop is truncated in the same clone), repair direction already sketched (say which copy was printed, or refuse — the horizon predicate ships in the same file with PR #10506). Awaiting first-touch grading; natural grading point is after PR #10506 merges, when the cloud_window_guard machinery this would reuse is on main.
Triage (first-touch): promoted → pm:queue (domain:devx, Task already set). The repair route is stated in the card and the horizon predicate already exists in the same file after PR #10506 (cloud_window_guard → scripts/pm/git-history.mjs): label which copy was printed, or refuse. Same-day churn note: scripts/collect-release-notes.sh was just touched by PR #10506 — sync against current origin/main before editing.
No open PR anywhere in the repo touches that file — all 29 open PRs enumerated at claim time.
The fence's actual members stay out of scope for you regardless: .changeset/**config and changeset scripts, cut-rc.yml, release.yml, pr-automation.ymlchangeset steps, root package.json.
⇒ Cleared to proceed on collect-release-notes.sh itself. If your own reading finds the file inside the fence after all, stop and report it rather than pressing on — I would rather be corrected than have you work around me.
Same-day churn
scripts/collect-release-notes.sh was touched by PR #10506 (#9902), which guarded section 4 of the same script. Sync against current origin/main before editing — main has moved repeatedly today, including the #10811 p0 landing at ~12:2xZ.
Standing constraints
⛔ Governed, human-merge-only: docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md — propose wording in the report.
⛔ Never edit content/docs/releases/**. This card is release tooling; the release pages are written centrally at release time.
⛔ Never git stash — refs/stash is shared by every worktree of the repo.
⚠️ A pipe swallows the exit code: capture as cmd > log 2>&1; ec=$?.
⚠️ Changeset gates pass vacuously against an uncommitted changeset.
{
"issue": 10509,
"status": "done",
"branch": "claude/issue-10509-changeset-body-fallback-signal",
"pr": "https://github.com/objectstack-ai/objectstack/pull/10844",
"premise_still_valid": false,
"summary": "The repair the card ASKED FOR is implemented (say which copy was printed, or refuse), but the card's stated MECHANISM is falsified and I verified that myself rather than inheriting it. The predicted route -- the per-file `|| git show PREV_REF:path` fallback firing because the deleting commit sits below a shallow floor -- is 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 present, so `del~1` resolves for every listed file; at the graft boundary git reports the whole tree as ADDED, never DELETED, so the file drops OUT of the list instead of reaching the fallback. Probed independently across 11 clone depths (3/5/8/9/10/11/12/15/20/25/40): zero unresolvable `del~1` at every depth. The harm the card named is real by a DIFFERENT route in the same section, and the same sweep reproduces it: the consumed-LIST itself silently truncates (0 files at depth 5, 1 at depth 12, 2 at depth 25, every one exit 0), and at the empty end 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. Section 2 now routes its range question through the same `git-history.mjs ensure --no-fetch` predicate section 4 uses (PR #10506) and withholds the list rather than printing it short; every body names the object it was read from; the unlabelled PREV_REF fallback is DELETED rather than guarded, since that copy is the pre-edit prose for any changeset revised mid-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`). NOTE ON THE DISPATCH BRIEFING: it said the branch carried no commits and there was no work to salvage. The REMOTE branch was indeed empty, but the local worktree from the pre-restart attempt held one unpushed commit plus a staged refinement -- I folded the staged delta in, rebased onto current origin/main, re-verified everything from scratch, and pushed. No second branch was opened.",
"tests": "All run at final commit 67afcb7e67 (tree clean, remote in sync). Gates derived with `node scripts/pm/dispatch-gates.mjs` with NO path arguments (it derived 1 path vs merge base 5f2e54cc6). Verdict lines, each as the gate itself printed it: check-cross-package-test-inputs -> 'OK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.'; check-nul-bytes -> 'check-nul-bytes: OK (scanned 6257 text file(s) -- 6257 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).'; the lint.yml step that actually covers this file (lint.yml:532-534) -> 'git-history --self-test: all cases passed.' + 'check-engine-split-ratio --self-test: all cases passed.' + 'collect-release-notes --self-test: all cases passed.' (21 cases, 21 green / 0 red). All heavy runs went through `bash scripts/pm/os-verify-lock.sh -c ...`; its own closing line read 'os-verify-lock: VERDICT command-exit 0 · held the lock 17s · waited 0s'. Exit codes captured as `cmd > log 2>&1; ec=$?`, never through a pipe. ABLATIONS -- this is a shell script, so there is no dist/ and no build leg; every mutation was instead confirmed ON DISK by marker counts before/after, never by an editor's exit code, and every leg was restored and re-verified. (1) Restored the old PREV_REF fallback: on-disk proof 'Body WITHHELD' 1->0 and the fallback line 0->1; result 2 cases RED, exit 1, and the failing output carried the literal fixture text 'SPANS-PREV pre-edit prose.' -- the card's harm reproduced head-on. (2) Unwired section 2's range guard: on-disk proof wiring call 1->0, 'if false; then' 0->1; result 1 case RED ('section 2 routes its range question through the guard'). (3) Deleted the `_source:` label: on-disk proof '_source: ' 2->1; result 1 case RED ('names the object it was read from'). RESTORE LEG (run explicitly, not assumed): `git status --porcelain` = 0 files, i.e. byte-identical to the commit, and the full self-test back to 21/21 green, exit 0. 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. p0 discipline: no date shape was touched; the self-test's window edges are already complete instants (2026-06-20T00:00:00Z), and git-history's own case pinning that is green.",
"open_questions": [],
"out_of_scope_findings": []
}
Fence re-checked independently, not inherited: #9465's own Declared file territory list names .changeset/**, the changeset scripts, cut-rc.yml, release.yml, pr-automation.yml and root package.json. scripts/collect-release-notes.sh is not among them — cleared, matching the dispatch resolution.
No changeset, skip-changeset applied and read back (size/m, skip-changeset): the diff is one file under scripts/**, no published package — the same publish-surface reasoning PR #10506 used on this same script.
One judgment call worth your eye: the PR says Fixes #10509, because the repair the card requested is delivered in full. If you would rather this card stay open to re-triage the falsified mechanism, downgrade that line to Part of #10509 before arming.
Activity
os-zhuang commentedon Aug 21, 2026
Triage:
finding+domain:devx, type Task — latent-by-the-card's-own-measure (bounded today: the file list feeding the loop is truncated in the same clone), repair direction already sketched (say which copy was printed, or refuse — the horizon predicate ships in the same file with PR #10506). Awaiting first-touch grading; natural grading point is after PR #10506 merges, when thecloud_window_guardmachinery this would reuse is on main.Generated by Claude Code
os-zhuang commentedon Aug 21, 2026
Triage (first-touch): promoted →
pm:queue(domain:devx, Task already set). The repair route is stated in the card and the horizon predicate already exists in the same file after PR #10506 (cloud_window_guard→scripts/pm/git-history.mjs): label which copy was printed, or refuse. Same-day churn note:scripts/collect-release-notes.shwas just touched by PR #10506 — sync against currentorigin/mainbefore editing.Generated by Claude Code
claude commentedon Aug 21, 2026
Claim:
/pm-dispatchdomain:devx execution seat (bug-first, batched round).Session:
session_01DdCnBGcHeufjrq7drTD3wtBranch:
claude/issue-10509-changeset-body-fallback-signalWorktree:
objectstack-issue-10509Domain:
domain:devxContainer & model:
M,mode:subagent,model: opusFile surface:
scripts/collect-release-notes.sh(section 2), and its self-test.Fence check, done before dispatch rather than left to the dev
scripts/collect-release-notes.sh— checked by reading the body, not by inferring from the title..changeset/**config and changeset scripts,cut-rc.yml,release.yml,pr-automation.ymlchangeset steps, rootpackage.json.⇒ Cleared to proceed on
collect-release-notes.shitself. If your own reading finds the file inside the fence after all, stop and report it rather than pressing on — I would rather be corrected than have you work around me.Same-day churn
scripts/collect-release-notes.shwas touched by PR #10506 (#9902), which guarded section 4 of the same script. Sync against currentorigin/mainbefore editing — main has moved repeatedly today, including the #10811 p0 landing at ~12:2xZ.Standing constraints
docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.md— propose wording in the report.content/docs/releases/**. This card is release tooling; the release pages are written centrally at release time.git stash—refs/stashis shared by every worktree of the repo.cmd > log 2>&1; ec=$?.git-history --self-testis red on main every day after 12:00 UTC — a bare--sincedate is approxidated to the CURRENT time of day, and it fails Lint & Repo Gates for every PR #10807 → PR fix(pm): give git-history self-test windows a complete instant, not an approxidated bare date #10811, merged):git-history --self-testused bare-date approxidates whose edge swept a fixture commit once a day. If you touch anything date-shaped in this script, spell complete instants (2026-06-20T00:00:00Z), never bareYYYY-MM-DD—collect-release-notes.sh --self-testalready spells its window that way and is the in-repo precedent.Report back to me; I review against GitHub and land it.
Generated by Claude Code
os-zhuang commentedon Aug 21, 2026
{ "issue": 10509, "status": "done", "branch": "claude/issue-10509-changeset-body-fallback-signal", "pr": "https://github.com/objectstack-ai/objectstack/pull/10844", "premise_still_valid": false, "summary": "The repair the card ASKED FOR is implemented (say which copy was printed, or refuse), but the card's stated MECHANISM is falsified and I verified that myself rather than inheriting it. The predicted route -- the per-file `|| git show PREV_REF:path` fallback firing because the deleting commit sits below a shallow floor -- is 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 present, so `del~1` resolves for every listed file; at the graft boundary git reports the whole tree as ADDED, never DELETED, so the file drops OUT of the list instead of reaching the fallback. Probed independently across 11 clone depths (3/5/8/9/10/11/12/15/20/25/40): zero unresolvable `del~1` at every depth. The harm the card named is real by a DIFFERENT route in the same section, and the same sweep reproduces it: the consumed-LIST itself silently truncates (0 files at depth 5, 1 at depth 12, 2 at depth 25, every one exit 0), and at the empty end 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. Section 2 now routes its range question through the same `git-history.mjs ensure --no-fetch` predicate section 4 uses (PR #10506) and withholds the list rather than printing it short; every body names the object it was read from; the unlabelled PREV_REF fallback is DELETED rather than guarded, since that copy is the pre-edit prose for any changeset revised mid-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`). NOTE ON THE DISPATCH BRIEFING: it said the branch carried no commits and there was no work to salvage. The REMOTE branch was indeed empty, but the local worktree from the pre-restart attempt held one unpushed commit plus a staged refinement -- I folded the staged delta in, rebased onto current origin/main, re-verified everything from scratch, and pushed. No second branch was opened.", "tests": "All run at final commit 67afcb7e67 (tree clean, remote in sync). Gates derived with `node scripts/pm/dispatch-gates.mjs` with NO path arguments (it derived 1 path vs merge base 5f2e54cc6). Verdict lines, each as the gate itself printed it: check-cross-package-test-inputs -> 'OK: 13 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.'; check-nul-bytes -> 'check-nul-bytes: OK (scanned 6257 text file(s) -- 6257 tracked, 0 untracked-not-ignored; skipped 5 binary; no raw ASCII control bytes).'; the lint.yml step that actually covers this file (lint.yml:532-534) -> 'git-history --self-test: all cases passed.' + 'check-engine-split-ratio --self-test: all cases passed.' + 'collect-release-notes --self-test: all cases passed.' (21 cases, 21 green / 0 red). All heavy runs went through `bash scripts/pm/os-verify-lock.sh -c ...`; its own closing line read 'os-verify-lock: VERDICT command-exit 0 · held the lock 17s · waited 0s'. Exit codes captured as `cmd > log 2>&1; ec=$?`, never through a pipe. ABLATIONS -- this is a shell script, so there is no dist/ and no build leg; every mutation was instead confirmed ON DISK by marker counts before/after, never by an editor's exit code, and every leg was restored and re-verified. (1) Restored the old PREV_REF fallback: on-disk proof 'Body WITHHELD' 1->0 and the fallback line 0->1; result 2 cases RED, exit 1, and the failing output carried the literal fixture text 'SPANS-PREV pre-edit prose.' -- the card's harm reproduced head-on. (2) Unwired section 2's range guard: on-disk proof wiring call 1->0, 'if false; then' 0->1; result 1 case RED ('section 2 routes its range question through the guard'). (3) Deleted the `_source:` label: on-disk proof '_source: ' 2->1; result 1 case RED ('names the object it was read from'). RESTORE LEG (run explicitly, not assumed): `git status --porcelain` = 0 files, i.e. byte-identical to the commit, and the full self-test back to 21/21 green, exit 0. 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. p0 discipline: no date shape was touched; the self-test's window edges are already complete instants (2026-06-20T00:00:00Z), and git-history's own case pinning that is green.", "open_questions": [], "out_of_scope_findings": [] }Fence re-checked independently, not inherited: #9465's own Declared file territory list names
.changeset/**, the changeset scripts,cut-rc.yml,release.yml,pr-automation.ymland rootpackage.json.scripts/collect-release-notes.shis not among them — cleared, matching the dispatch resolution.No changeset,
skip-changesetapplied and read back (size/m,skip-changeset): the diff is one file underscripts/**, no published package — the same publish-surface reasoning PR #10506 used on this same script.One judgment call worth your eye: the PR says
Fixes #10509, because the repair the card requested is delivered in full. If you would rather this card stay open to re-triage the falsified mechanism, downgrade that line toPart of #10509before arming.Generated by Claude Code
Generated by Claude Code