Repository navigation
Verify changelog claims against their own sources, and gate on a clean run - #4051
Conversation
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
left a comment
There was a problem hiding this comment.
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.
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
left a comment
There was a problem hiding this comment.
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.
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
) #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>
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.mdas 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.mdas 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.
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
29a611b7cstates 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.mdruns the arm in a checkout atv0.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## Unreleasedand 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.
🤖 Generated with Claude Code