Skip to content

fix(generator-cli): don't diff autoversion against a stale generation baseline - #17392

Open
willkendall01 wants to merge 1 commit into
mainfrom
will/autoversion-stale-generation-baseline
Open

fix(generator-cli): don't diff autoversion against a stale generation baseline#17392
willkendall01 wants to merge 1 commit into
mainfrom
will/autoversion-stale-generation-baseline

Conversation

@willkendall01

@willkendall01 willkendall01 commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Description

Linear ticket: Refs

Autoversioning picks its diff baseline from .fern/replay.lock's current_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 the git cat-file -e probe, 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:

  • One SDK computed MAJOR: v1.2.0 → v2.0.0, headlining a field rename that had already shipped in v1.2.0.
  • The other three sent diffs inflated by a whole release, timed out against the FAI service (HTTP 504), and fell back to a neutral PATCH bump with an empty changelog entry.

The ADR 0002 escape hatch could not have saved them either: findPreviousGenerationFromHistory matched subject.startsWith("[fern-generated]"), but a squash merge replaces the subject with the PR title and demotes the marker to a body bullet:

feat: 1.3.0 alignExpr (#15)

* [fern-generated] Update SDK
* [fern-autoversion] SDK regeneration

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.

  • Freshness, not just reachability. resolveReachableGenerationBase now 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 via git 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.
  • Match the marker on the full message. New exported isGenerationCommitMessage tests each line of %B after stripping whitespace, quote markers, and a single list bullet — so a squashed * [fern-generated] Update SDK matches, while prose that merely mentions the marker (revert: undo the [fern-generated] commit) does not. The git log format becomes %H%x00%B%x1e, since %B spans lines and needs a record separator.
  • Docs: ADR 0003, a superseded-in-part note on ADR 0002 (its "squash-merge-resilient" claim did not hold as written), and the package CLAUDE.md gotchas.
  • Updated README.md generator (if applicable) — n/a

Testing

  • Unit tests added/updated — new 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 is 1.3.0 → 1.4.0, not 1.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; plus isGenerationCommitMessage unit cases.
  • Full @fern-api/generator-cli suite green (485 tests), tsc clean, biome formatted.
  • Manual testing — not run against a live generation; the failure was reproduced from production logs and repo state rather than locally.

Follow-up, not in this PR

Reconciling the lockfile after a release merges (so current_generation doesn'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: a null baseline currently degrades to a no-op release that is indistinguishable from "nothing changed."


Open in Devin Review

… 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>
@willkendall01 willkendall01 self-assigned this Aug 13, 2026

@nitpickybot nitpickybot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Reviewed the changes — everything looks good. No issues found.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 3 additional findings.

Open in Devin Review

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.

1 participant