fix(generator-cli): don't diff autoversion against a stale generation baseline - #17392
Open
willkendall01 wants to merge 1 commit into
Open
fix(generator-cli): don't diff autoversion against a stale generation baseline#17392willkendall01 wants to merge 1 commit into
willkendall01 wants to merge 1 commit into
Conversation
… baseline `.fern/replay.lock`'s `current_generation` was trusted whenever it pointed at a reachable commit. A squash-merged release never advances it, and the commit it points at stays on the default branch forever — so every later regeneration diffed against the pre-release tree and re-reported changes the release had already shipped. Two changes, neither sufficient alone: - Check freshness, not just reachability. When history contains a generation commit that strictly descends from the recorded one, it becomes the diff base. Descent is required so an unrelated or older commit can never walk the baseline backwards. - Match the generation marker on the full commit message (`%B`). A squash merge replaces the subject with the PR title and demotes `[fern-generated]` to a body bullet, so the subject-prefix scan found nothing and silently produced a no-op release. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Linear ticket: Refs
Autoversioning picks its diff baseline from
.fern/replay.lock'scurrent_generation, and trusted that value whenever it pointed at a reachable commit. Reachable is not the same as current.When a release PR is squash-merged, nothing advances
current_generation, so it keeps pointing at the commit that preceded the release. That commit is a merge commit on the default branch — it stays reachable forever, passes thegit cat-file -eprobe, and gets used verbatim. Every later regeneration then diffs the new SDK against the pre-release tree and re-reports everything the release already shipped.Seen in production across four SDK repos for one customer, drifted one release behind for ten weeks:
MAJOR: v1.2.0 → v2.0.0, headlining a field rename that had already shipped in v1.2.0.The ADR 0002 escape hatch could not have saved them either:
findPreviousGenerationFromHistorymatchedsubject.startsWith("[fern-generated]"), but a squash merge replaces the subject with the PR title and demotes the marker to a body bullet:So on a squash-merging repo the scan finds nothing, returns
null, and the diff is treated as empty — a silent no-op release. The marker it needs is in the commit, one field away from where it looked.Changes Made
Two changes in
AutoVersionStep. Neither is sufficient alone — the first forces the question, the second answers it.resolveReachableGenerationBasenow derives the history baseline unconditionally and prefers it over a reachable recorded SHA only when the derived commit strictly descends from it (git merge-base --is-ancestor, plus a full-SHA inequality check viagit rev-parse --verify). The descent requirement is what keeps this conservative: a recorded SHA can legitimately be newer than anything on the first-parent line (e.g. it points at the previous run's generation commit, still reachable via the open bot branch), and an unrelated or older commit must never displace it.isGenerationCommitMessagetests each line of%Bafter stripping whitespace, quote markers, and a single list bullet — so a squashed* [fern-generated] Update SDKmatches, while prose that merely mentions the marker (revert: undo the [fern-generated] commit) does not. Thegit logformat becomes%H%x00%B%x1e, since%Bspans lines and needs a record separator.CLAUDE.mdgotchas.Testing
auto-version-step.stale-baseline.test.ts(8 tests) builds real git history matching a squash-merged release and covers: re-anchoring off a stale-but-reachable SHA (asserting the diff handed to FAI contains only the new work and the bump is1.3.0 → 1.4.0, not1.2.0-based); the guard that keeps a recorded SHA when the derived commit is not a descendant; re-anchoring on a squash body when the recorded SHA is unreachable; plusisGenerationCommitMessageunit cases.@fern-api/generator-clisuite green (485 tests),tscclean, biome formatted.Follow-up, not in this PR
Reconciling the lockfile after a release merges (so
current_generationdoesn't go stale in the first place) needs a post-merge hook or a pass inside replay, and would leave already-drifted repos broken until it ran. This consumer-side guard is a prerequisite either way. Also worth revisiting: anullbaseline currently degrades to a no-op release that is indistinguishable from "nothing changed."