Skip to content

(fix): accept generated notes on the draft release - #90

Merged
abnegate merged 1 commit into
mainfrom
fix/draft-release-generated-notes
Aug 21, 2026
Merged

(fix): accept generated notes on the draft release#90
abnegate merged 1 commit into
mainfrom
fix/draft-release-generated-notes

Conversation

@abnegate

Copy link
Copy Markdown
Member

Fixes run 32446810818, which tagged 2.0.1, drafted the release, then died on Draft release 374170913 is unsafe.

The defect

GitHub::createDraft asks GitHub for generated notes:

['-F', 'generate_release_notes=true'],

so the release GitHub returns has the automation's body plus the generated changelog appended. Both draft checks then compared that body for equality against what was sent:

|| $release->body !== $body// GitHub::assertDraft
|| $draft->body !== $body// Orchestrator::validateDraft

That comparison can never hold while notes are generated, so the release step could not succeed on any run. It is not a flake — every dependency release would have died at exactly this point, after the tag and the draft already existed, leaving the half-finished state recovery then has to unpick.

The rejected draft is intact and correct — tag_name: 2.0.1, target_commitish: 8a93696, draft: true, markers present. Only the equality check was wrong.

Why a prefix check, not dropping the generated notes

Dropping generate_release_notes would also fix it, but the generated changelog is what previous releases shipped and is worth keeping. The property the safety check actually depends on is that the automation's markers — dependency-automation:v1, dependency-target:<sha>, dependency-pull:<n> — are present and unaltered at the head of the body. str_starts_with asserts exactly that, and everything else in the check (tag, target, pull, draft, prerelease) is unchanged.

RecoverySelector::matches already reads the markers rather than demanding an exact body, so this makes the two paths consistent.

Diagnosability

Draft release <id> is unsafe named none of the six fields it compared, which is why a guaranteed failure needed the API response and the source side by side to explain. Both sites now list which fields mismatched.

Verification

  • test_accepts_a_draft_with_generated_notes_appended reproduces the production failure: a draft whose body is the automation body plus a ## What's Changed block. Confirmed it errors with is unsafe against the old equality check and passes with the fix.
  • test_rejects_a_draft_whose_markers_were_rewritten keeps the safety property honest — a draft carrying a different dependency-pull marker is still rejected, with the new message naming the body.
  • composer verify: Pint, PHPStan max, 154 tests / 1815 assertions, 91 parity contracts.

Not verified

  • No end-to-end release has run with this fix. The next dispatch is the real test, and the draft/tag left behind by the failed run has to be cleared first (see the PR discussion) or the run will find a stale 2.0.1.
  • The existing tests fed the fake API the same body they sent, which is precisely why this shipped. The new test uses a body the fake does not echo back, but the fake is still a fake — only a live run exercises GitHub's actual note generation.

createDraft requests generate_release_notes, so GitHub returns the body it
was sent with the generated changelog appended. Both draft checks then
compared that body for equality against what was sent, so the comparison
could never hold and every release died after the tag and draft already
existed — leaving exactly the half-finished state recovery has to unpick.
Require the body to open with the automation markers instead. That is the
property the safety check depends on, and RecoverySelector already reads
the markers this way rather than demanding an exact body.
Name the mismatching fields too: the message listed none of the six it
compared, which is why a guaranteed failure took a release run to explain.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@greptile-apps

Copy link
Copy Markdown

Greptile Summary

This PR allows GitHub-generated release notes to follow the automation-owned draft body while retaining exact validation of release metadata and marker content.

  • Replaces exact body equality with full-prefix validation in both draft safety checks.
  • Expands unsafe-draft errors to identify every mismatched field.
  • Adds regression coverage for generated-note suffixes and rewritten markers.
  • Documents the release failure and diagnostic improvements in CHANGES.md.

Confidence Score: 5/5

The PR appears safe to merge, with the intended suffix relaxation bounded by independent release metadata and marker validation.

The expected automation body must still match exactly at the beginning of the draft, all release state fields remain checked, and final publication additionally validates marker uniqueness and merge correspondence.

Important Files Changed

FilenameOverview
.github/scripts/src/Automation/Orchestrator.phpAccepts appended release notes during final draft validation while preserving tag and recovery-marker checks and reporting individual mismatches.
.github/scripts/src/Automation/Repository/GitHub.phpValidates the complete expected body as a prefix of GitHub's returned draft and retains exact checks for tag, target, pull, draft, and prerelease state.
.github/scripts/tests/Unit/Automation/OrchestratorTest.phpAdds focused regression tests proving generated notes are accepted while rewritten automation markers remain rejected.
CHANGES.mdRecords the generated-release-notes incompatibility and improved mismatch diagnostics.

Reviews (1): Last reviewed commit: "(fix): accept generated notes on the dra..." | Re-trigger Greptile

@abnegate
abnegate merged commit f83d96b into mainAug 21, 2026
12 checks passed
@abnegate
abnegate deleted the fix/draft-release-generated-notes branch August 21, 2026 04:45
Sign up for freeto 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.

1 participant

@abnegate