> For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://buildwithfern.com/learn/llms.txt. # Migrating to Replay > Step-by-step guide for switching SDK generation from release or push mode to pull-request mode so Fern Replay can run. This guide migrates an SDK from `release` or `push` output mode to `pull-request` output mode — the mode required to enable [Fern Replay](/learn/sdks/overview/custom-code#replay). Only follow it if your SDK is currently on `release` or `push` mode and you want to switch in order to use Replay. SDKs already using `pull-request` mode have Replay on by default; no migration is needed. ## What changes * SDK updates arrive as PRs instead of direct pushes to `main`. * Customizations to generated code survive regeneration via 3-way merge. * Version bumps are computed from pure generator output, never contaminated by customizations. * Publishing is decoupled from generation, so you control when to release. ## Before you start * Install the [Fern GitHub App](https://github.com/apps/fern-api) on your SDK repositories. * Update to the latest Fern CLI: `fern upgrade`. * Update to the latest SDK generator versions: `fern generator upgrade`. ## Phase 1: Move publishing secrets to the SDK repo Fern sets GitHub Actions secrets in your SDK repo on every generation. When you switch to `pull-request` output mode, that write would clobber any secrets you set directly. To avoid this, move secret ownership from the config repo to the SDK repo before flipping modes. For each SDK repo: #### Identify the publishing secrets Find which secrets your `generators.yml` references for publishing: * Python: `PYPI_USERNAME`, `PYPI_PASSWORD` * TypeScript / npm: `NPM_TOKEN` * Java / Maven: `MAVEN_USERNAME`, `MAVEN_PASSWORD`, `MAVEN_SIGNATURE_KID`, `MAVEN_SIGNATURE_PASSWORD`, `MAVEN_SIGNATURE_SECRET_KEY` * Ruby: `RUBY_GEMS_API_KEY` * C# / NuGet: `NUGET_API_KEY` * Rust / Crates: `CRATES_TOKEN` #### Add credentials to the SDK repo Add the real publish credentials directly to the SDK repo: **Settings → Secrets and variables → Actions → New repository secret**. #### Remove credentials from \`generators.yml\` Remove the publish credential values from the config repo's `generators.yml`: **`generators.yml`** ```yaml title="generators.yml" # BEFORE groups: python-sdk: generators: - name: fern-python-sdk version: "..." github: repository: customer/python-sdk mode: pull-request output: pypi: username: $PYPI_USERNAME password: $PYPI_PASSWORD # AFTER (remove the output/pypi block entirely) groups: python-sdk: generators: - name: fern-python-sdk version: "..." github: repository: customer/python-sdk mode: pull-request ``` #### Verify secrets are preserved Run a generation. Confirm the SDK repo's secrets weren't overwritten by checking the "Last updated" timestamp under **SDK repo → Settings → Secrets**. ## Phase 2: Switch output mode In `generators.yml`: **`generators.yml`** ```yaml title="generators.yml" {3} github: repository: customer/python-sdk mode: pull-request # was: mode: release (or: mode: push) ``` If you use `version: AUTO`, no other changes are needed. Autoversioning runs as part of the generator-cli pipeline and diffs pure generator output, so customer customizations never contaminate the version diff. ## Phase 3: Decouple publishing from generation (optional) To control publishing on your own schedule rather than on every Fern generation, keep your publish workflow out of Fern's generation cycle: #### Add the workflow to .fernignore **`.fernignore`** ```text title=".fernignore" .github/workflows/ci.yml ``` #### Change the workflow trigger Switch from push-to-main to manual or release-tagged: **`.github/workflows/ci.yml`** ```yaml title=".github/workflows/ci.yml" # Instead of: on: push: branches: [main] # Use: on: workflow_dispatch: release: types: [published] ``` #### Update secret references Update any secret references if you renamed credentials in Phase 1. ## Phase 4: First generation with Replay #### Run fern generate Run `fern generate --group ` from the config repo (or trigger via your existing CI). #### Confirm the lockfile was created The first run auto-creates `.fern/replay.lock`. Replay tracks customer commits to generated files from this point on. #### Review the resulting PR The PR contains: * `[fern-generated]`: pure generator output * `[fern-replay]`: patches re-applied (empty on first run, since no patches exist yet) * Updated `.fern/replay.lock` #### Squash-merge the PR The next generation re-anchors correctly even after the squash. ## Phase 5: Validate After the first generation and merge, confirm the following: * A PR was created (not a direct push to `main`). * No unintended package release was triggered. * SDK code is correct and passes CI. * `.fern/replay.lock` exists in the SDK repo. * `.fernignore` contains `replay.lock` (and `replay.yml`). After your first real customization, verify Replay's behavior end-to-end: * You edit a generated file, commit, and merge to `main`. * The next `fern generate` detects the patch (PR body shows `patches detected: 1`). * The customization survives regeneration via a clean 3-way merge. * `.fern/replay.lock` shows the patch stored. ## Rollback Replay never modifies files destructively. Your `main` branch always has correct code, so rolling back is straightforward. **Quick disable.** To stop Replay while staying in `pull-request` mode, set `replay.enabled: false` in your `generators.yml` (see the [`replay` reference](/learn/sdks/reference/generators-yml#replay)). The next `fern generate` skips the patch detection and application phase; PRs land in the same shape as before, just without the `[fern-replay]` commit. **Full revert to `release` or `push` mode:** #### Switch mode back in generators.yml **`generators.yml`** ```yaml title="generators.yml" {3} github: repository: customer/python-sdk mode: release # or mode: push ``` #### Re-add publish credentials to the config repo Re-add the credentials you removed in Phase 1. #### Clean up Replay state in the SDK repo * Delete `.fern/replay.lock` * Delete `.fern/replay.yml` * Delete the `fern-generation-base` tag if it exists #### Close any open fern-bot PRs from PR mode ## Troubleshooting | Command | Effect | | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | | [`fern generate --no-replay`](/learn/cli-api-reference/cli-reference/sdk-commands#no-replay) | Skip patch application for one generation. Generation still creates `[fern-generated]`; patches aren't applied. Useful for debugging. | | [`fern replay forget`](/learn/cli-api-reference/cli-reference/sdk-commands#fern-replay-forget) | Untrack patches by ID, by pattern, or all at once. The next generation overwrites those files. | ## Known caveats * **Closed-without-merge replay PRs.** If a replay PR is closed without merging, the next generation re-derives its anchor from the current branch history. No manual cleanup needed. * **Force pushes and rewritten history.** Replay re-derives its scan anchor from `git log` on every run, so force-pushed branches continue to work and existing patches still apply correctly. > Step-by-step guide for switching SDK generation from release or push mode to pull-request mode so Fern Replay can run.