Skip to content

fix(cli): don't crash fern upgrade when the source version is a range pin - #17340

Open
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1786048474-fix-upgrade-unparseable-from-version
Open

fix(cli): don't crash fern upgrade when the source version is a range pin#17340
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1786048474-fix-upgrade-unparseable-from-version

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Description

fern upgrade dies with Failed to parse version: * / Failed to rerun CLI at version <target> when the resolved migration source version isn't something parseVersion understands.

Repro: a fern.config.json whose committed version is "*" (also latest, 1.x). resolveSourceVersion falls back to git show HEAD:fern.config.json when neither --from nor FERN_PRE_UPGRADE_VERSION is set, gets *, passes --from * to the newly installed CLI, and runMigrationsgetMigrationsToRunisVersionAheadparseVersion("*") throws. The existing version === "latest" || version === "*" guard in upgrade() only covers the no-upgrade-available branch, so it never fires here. Note the working copy can hold a perfectly valid version — git HEAD wins.

resolveSourceVersion now returns string | undefined and validates its result:

if (isValidVersion(resolvedFromVersion)) return resolvedFromVersion;
if (!isLocalDev && isValidVersion(cliVersion)) return cliVersion; // best estimate of what the project has been running
logger.warn("Skipping migrations because ... is not a version that can be migrated.");
return undefined;

undefined means skip migrations: the migration block is guarded, and --from / FERN_PRE_UPGRADE_VERSION are omitted from the rerun instead of forwarding a poisoned value. fern.config.json is still rewritten to the target version.

Changes Made

  • resolveSourceVersion validates its result via isValidVersion, falling back to the running CLI version, then to skipping migrations
  • Migration run + rerun args/env made conditional on having a parseable source version

Testing

  • Unit tests added/updated — upgrade.test.ts now uses the real isValidVersion (only isVersionAhead is mocked) and covers "*" in the rerun path, the already-at-target path, and the local-dev skip path. 50 tests pass.

  • Manual testing completed. Clean repo, committed fern.config.json = "*", working copy = 5.57.0.

    Before, with the published CLI:

    $ npx fern-api@5.57.0 upgrade
    Upgrading from 5.57.0 → 5.90.1
    Running migrations from * → 5.90.1
    Failed to parse version: *
    Failed to rerun CLI at version 5.90.1        # exit 1
    

    After, with this branch built (pnpm fern-dev:build):

    $ fern upgrade --version 5.90.1 --yes
    Skipping migrations because * is not a version that can be migrated.
    Updated fern.config.json to version 5.90.1   # exit 0
    

Link to Devin session: https://app.devin.ai/sessions/5ed9ce4d0f064626b2f66a2456abfb9a


Open in Devin Review

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@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.

AI Review Summary

Sensible fix: resolveSourceVersion now validates its output and returns undefined to mean "skip migrations", with the migration block and rerun args/env guarded accordingly. Tests use the real isValidVersion, which is a good call. Two minor concerns: the env: {} fallback doesn't actually unset a poisoned FERN_PRE_UPGRADE_VERSION inherited from the parent process, and the "Skipping migrations" warning is emitted even on the rerun path where the child will still run migrations.

  • 🟡 1 warning(s)
  • 🔵 1 suggestion(s)

env: {
[PREVIOUS_VERSION_ENV_VAR]: resolvedFromVersion
},
env: resolvedFromVersion != null ? { [PREVIOUS_VERSION_ENV_VAR]: resolvedFromVersion } : {},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 warning

env: {} doesn't remove an inherited FERN_PRE_UPGRADE_VERSION — execa merges env with process.env by default, so if the parent itself was invoked with a poisoned value (e.g. FERN_PRE_UPGRADE_VERSION=*), the child still sees it and re-resolves the same unparseable version. It won't crash today (the child falls back to its own CLI version), but the stated intent "omitted from the rerun instead of forwarding a poisoned value" isn't met. Consider explicitly clearing it, e.g. { [PREVIOUS_VERSION_ENV_VAR]: resolvedFromVersion ?? undefined } (if rerunFernCliAtVersion's env type allows undefined), or pass an empty string that the child's validation already rejects.

return cliVersion;
}

cliContext.logger.warn(`Skipping migrations because ${resolvedFromVersion} is not a version that can be migrated.`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔵 suggestion

This warning fires from both call sites, including the rerun path — but there the child CLI will re-resolve a source version and likely do run migrations, so "Skipping migrations" is misleading to the user. Consider wording it in terms of the resolution failure (e.g. "${resolvedFromVersion}" is not a version that can be migrated from; migrations will be skipped for this step.) or only warning when migrations are actually being skipped in-process.

Also: the CLI-version fallback above is logged at debug, which hides a decision that can silently change which migrations run. info would be more honest.

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

Copy link
Copy Markdown
Contributor Author

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 bugs or issues to report.

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