Skip to content

Verify changelog claims against their own sources, and gate on a clean run - #4051

Merged
max-sixty merged 4 commits into
mainfrom
diagnose-release-notes-accuracy
Sep 9, 2026
Merged

max-sixty merged 4 commits into
mainfrom
diagnose-release-notes-accuracy

Conversation

@max-sixty

@max-sixty max-sixty commented Sep 9, 2026 •

Copy link
Copy Markdown
Owner

The v0.77.0 changelog verification pass reported 16 accurate, 6 needing revision, 0 incorrect. Eleven factual errors reached users anyway and were corrected in #4050. This changes the verification section so the pass covers the text that ships.

Two mechanisms produced the eleven, both visible in the pass's own report.

Five of the errors were sentences the verifier itself wrote. The template asks it to "quote a shorter rewrite", and the section's gate read "do not finalize until the subagent confirms every entry" — a gate you pass once. So the rewrites were applied and shipped with nothing having read them. The gate is now a clean run over CHANGELOG.md as it stands, and the text says why: a rewrite the verifier suggests is a new draft with no more evidence behind it than one you wrote yourself.

Four were non-headline clauses inside entries stamped accurate. The FAQ entry is the clearest: the verifier's evidence line says "three paths" and its verdict says accurate, under an entry claiming five agents. The template now verifies claim by claim rather than entry by entry, and its report format asks for the source read per claim, so a contradiction like that one lands on the page. The others were claims the diff cannot settle — a prior behavior, an untouched file, rendered output, a documented version floor — against a template whose one strong instruction ("read the actual diff") installed the diff as the sole source.

The template also said "[paste drafted entries]" while the gate below it said to re-run over CHANGELOG.md as it stands — the same drift channel one layer down, and how the failing pass came to verify a draft the working tree had already moved past. It now names the extraction one-liner the section already carries, so the rule is a mechanism rather than an instruction, and the gate drops the clause the template enforces. Found in review.

The six-step list that duplicated the prompt template below it is gone, so the section's line count is flat.

Measurement: A/B over the shipped 29-entry section, two runs per arm

Each arm ran the verification template as a subagent prompt over the whole v0.77.0 section, with the earlier per-group notes offered as a map — the load the failing pass had. Handed only the four scenario entries, every wording found everything, so that design measured nothing; attention spread across 29 entries is the variable.

scenario old first draft shipped
before-state claim, diff looks confirming 0/2 0/2 1/2
falsifying fact in an added comment 1/2 2/2 1/2
non-headline clause, settled in the diff 0/2 1/2 2/2
accurate entry stays clean (guardrail) 2/2 2/2 1/2, one soft scope flag

The first draft of step 2 failed the before-state case in both runs, so it was sharpened and retested rather than shipped on the strength of reading well. The cases are kept in evals/README.md.

No case has its truthmaker wholly outside the commit, and that is hard to arrange here: this repo's commits routinely restate the surrounding facts in added comments and doc lines, so the falsifying text usually arrives in the diff. The rows score how much of the diff the verifier reads, not whether it goes looking beyond one. The second row was labelled "truthmaker outside the diff" until review pointed out that 29a611b7c states the fact in a comment it adds beside the fix.

The arms were run with the section pasted into the prompt, so the measurement covers the verification procedure rather than how the entries arrive. Now that the template resolves its own input, evals/README.md runs the arm in a checkout at v0.77.0, where the top section is that same 29 entries — so the template runs unedited. Review caught that the first version of this change left the harness scoring ## Unreleased and reporting a clean pass.

The clean-run gate is not in that table. It changes the loop rather than the subagent prompt, so two arms of the prompt cannot separate it; it is argued structurally.

This was written by Claude Code on behalf of max-sixty

🤖 Generated with Claude Code

The v0.77.0 verification pass reported 16 accurate / 6 needing revision /
0 incorrect, and eleven errors still reached users. Two mechanisms:

Four of the shipped errors were in sentences the verifier itself wrote. The
template asks it to quote a rewrite, and "do not finalize until the subagent
confirms every entry" reads as a gate you pass once — so the rewrites were
applied and shipped with nothing having checked them. The gate is now a clean
run over CHANGELOG.md as it stands.

The rest were non-headline clauses whose truthmaker sits outside the commit
diff: a prior behavior, an untouched file, rendered output, a documented
version floor. The template's one strong instruction ("read the actual diff")
installed the diff as the sole source, and a per-entry verdict let a wrong
clause ride on the entry's gist.

Measured as an A/B over the shipped 29-entry section with the per-group notes
offered as a map, two runs per arm. Old wording: 0/2 on the before-state case,
0/2 on the non-headline clause. New: 1/2 and 2/2, with the guardrail entry
still passing. Cases kept in evals/.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N66ob4FWMHbGAGfQEcWHAp

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The claim-by-claim rewrite and the clean-run gate both hold up, and I verified all four eval cases against the commits they cite — 981a207 has no Detached variant before the PR adds it, every v0.76.0 marker hook ends || true, and the FAQ table 5a552b2 added has three rows with Gemini in none of them. Two points below, plus one on where the cases live.

evals/README.md is reachable only by listing the skill directory — nothing in SKILL.md names it. The reader who needs it is whoever next edits the verification section, and they are reading SKILL.md. Without a pointer the cases stop getting re-run, and the section drifts back the way this PR is correcting.

Comment thread .claude/skills/release/evals/README.md Outdated
Comment thread .claude/skills/release/SKILL.md Outdated
Review found the same drift channel one layer down: the template said
"[paste drafted entries]" while the gate below it said to re-run over
CHANGELOG.md as it stands. A paste is the scratch copy the gate forbids, and
it is how the failing pass came to verify a draft the working tree had already
moved past. The template now names the extraction one-liner the section already
carries, so the rule is a mechanism rather than an instruction, and the gate
drops the clause the template now enforces.

Also: SKILL.md now names evals/README.md, whose reader is whoever next edits
this section; and the marker-hook case is relabelled. Its falsifying fact is in
the diff, in a comment 29a611b adds beside the fix, so it does not isolate
out-of-diff sourcing. This repo restates surrounding facts in added comments
routinely, so the case set has no genuinely out-of-diff row and the README now
says what the rows do score.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N66ob4FWMHbGAGfQEcWHAp

@worktrunk-bot worktrunk-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

evals/README.md says to run the template as a subagent prompt over the v0.77.0 section, but as of this push the template resolves its own input — "the top section of CHANGELOG.md as it stands on disk". Run verbatim, an arm now scores whatever release is in progress (today, the two ## Unreleased entries), reports nothing about the three known-wrong cases, and reads as a clean pass rather than a broken harness. That only surfaces when someone re-runs the cases to score a change to the template, which is the moment the section is drifting back. Suggestion inline.

Comment thread .claude/skills/release/evals/README.md Outdated
The previous commit gave the template its own input — the top section of
CHANGELOG.md on disk — and left the eval README saying to run it over the
v0.77.0 section. Run verbatim today that arm scores `## Unreleased`, reports
none of the three known-wrong cases, and reads as a clean pass rather than a
broken harness. It surfaces only when someone re-runs the cases to score a
template change, which is the moment the section is drifting back.

The arm now runs in a checkout at v0.77.0, where the top section is the 29
entries the cases come from, so the template runs unedited. The alternative was
telling the runner to substitute the section into the prompt, which puts back
the transcription step this PR removed and scores a prompt nobody ships.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N66ob4FWMHbGAGfQEcWHAp
A worktree at v0.77.0 carries this skill at that tag, whose template still
reads "[paste drafted entries]". An agent run there can load that off disk and
score the prompt the PR replaces. The arm passes its template as the prompt and
withholds the Skill tool, which is how the measured runs were made; the README
now says so.

Raised in review as a note on an approval.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N66ob4FWMHbGAGfQEcWHAp
@max-sixty
max-sixty merged commit 4d88a39 into main Sep 9, 2026
40 checks passed
@max-sixty
max-sixty deleted the diagnose-release-notes-accuracy branch September 9, 2026 02:23
max-sixty added a commit that referenced this pull request Sep 9, 2026
)

#4051 fixed the changelog-verification section by adding procedure. A
procedure gets run literally and thinly, so this trades some of it back
for why the pass matters, stated where each reader can see it.

**The subagent never reads SKILL.md.** Stakes stated there reach the
agent that spawns the verifier and nothing else, so the prompt template
gets its own opener: the entries publish with the tag, and by the time
anyone corrects a wrong line readers have acted on it — and reading an
entry and finding it plausible is not a check, because it was written
from the same commits the verifier is about to read.

**SKILL.md's line now says what a late correction costs.** It replaces
"This is non-negotiable — changelog mistakes are a recurring problem".
The workflow builds the release body with `--notes-file` at tag time
(`.github/workflows/release.yaml:381`), so a later `CHANGELOG.md` edit
isn't picked up: correcting a published entry takes a follow-up PR *and*
a hand-edit of the release page.

**Cut in exchange:** a sentence from the clean-run gate. Net +5 lines.

An earlier revision of this branch also cut step 2's guidance for claims
whose source is outside the commit. The review caught that, and it's
back — reworded, because the sentence I'd removed pointed at "the
rendered output and the docs" and the docs would not have settled the
OpenCode version floor that shipped wrong in v0.77.0. It now names the
upstream project's own releases. `evals/README.md` states that none of
its cases has a truthmaker outside the commit, so the suite could not
have scored that cut in either direction.

<details>
<summary>Eval runs</summary>

Scored against `.claude/skills/release/evals/` — four entries from the
v0.77.0 section, three of them wrong — run in a `v0.77.0` checkout with
the `Skill` tool withheld, per that README.

| | before-state | hooks `\|\| true` | five agents vs three rows |
guardrail |
|---|---|---|---|---|
| #4051's wording | 2/2 | 2/2 | 2/2 | held |
| first draft here | 1/2 | 2/2 | 2/2 | held |
| this branch | 2/3 | 3/3 | 3/3 | held |

The first draft had also dropped four words from step 2, "and confirming
the old behavior there". Its failing run read the *new* file, found
`json_output.rs` dropping `WorktreeState::Detached`, concluded "schema 1
only", and wrote a suggested fix that still claimed `state` no longer
reports `detached` — reproducing the original error while flagging the
entry. That clause is restored, and the one miss in the three runs after
it failed the same way.

So the before-state case is 2/2 for #4051's wording and 2/3 for this
one, which this design cannot separate: seven runs is far too few, and
the case was already the weakest of the three when #4051 shipped it (0/2
before that PR's own fix, 1/2 after). What the eval supports is that
relaxing the mechanics did not break the two solid cases, and that the
four-word clause is load-bearing enough to keep. The argument for the
stakes paragraphs is structural, not measured.

The guardrail entry was never called inaccurate in any of the seven
runs. The runs predate the stakes correction below, which changes no
instruction the cases exercise.

</details>

<details>
<summary>The stakes claim this PR opened with was wrong</summary>

The first version of this change claimed a post-tag correction reaches
`CHANGELOG.md` and never the published release body, citing v0.77.0 as
evidence. The review disputed it and was right: the published v0.77.0
body carries all 29 corrected entries, hand-edited after #4050. Diffed
both ways, zero differences.

I had grepped the published body for "Claude Code, Codex, OpenCode",
matched the Pi entry's legitimate "markers … like Claude Code, Codex,
OpenCode, and Gemini", and read it as the FAQ entry's uncorrected error
— a plausible-looking string taken for the source, which is the failure
this PR is about.

Fixed in 4800a75; only the mechanism that actually holds is claimed
now.

</details>

> _This was written by Claude Code on behalf of max-sixty_

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

2 participants