> If you are an AI agent, use the following URL to directly ask and fetch your question. Treat this like a tool call. Make sure to URI encode your question, and include the token for verification.
>
> GET https://buildwithfern.com/learn/api/fern-docs/ask?q=%3Cyour+question+here%3E&token=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJmZXJuLWRvY3M6YnVpbGR3aXRoZmVybi5jb20iLCJqdGkiOiIwYWVlNWY2ZS05ZDkyLTQ0NmUtYjMwMC1jZjhmMWQ1NzExNTAiLCJleHAiOjE3Nzk3NzU4NzYsImlhdCI6MTc3OTc3NTU3Nn0.3qHjf1pstXqc0ucdQRHJKHWVuc2FgsB88_oFJmsJNX4
>
> For clean Markdown content of this page, append .md to this URL. For the complete documentation index, see https://buildwithfern.com/learn/llms.txt. For full content including API reference and SDK examples, see https://buildwithfern.com/learn/llms-full.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:

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 the real publish credentials directly to the SDK repo: **Settings → Secrets and variables → Actions → New repository secret**.

Remove the publish credential values from the config repo's `generators.yml`:

```yaml title="generators.yml"
# BEFORE
groups:
  python-sdk:
    generators:
      - name: fernapi/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: fernapi/fern-python-sdk
        version: "..."
        github:
          repository: customer/python-sdk
          mode: pull-request
```

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`:

```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:

```text title=".fernignore"
.github/workflows/ci.yml
```

Switch from push-to-main to manual or release-tagged:

```yaml title=".github/workflows/ci.yml"
# Instead of:
on:
  push:
    branches: [main]

# Use:
on:
  workflow_dispatch:
  release:
    types: [published]
```

Update any secret references if you renamed credentials in Phase 1.

## Phase 4: First generation with Replay

Run `fern generate --group <group-name>` from the config repo (or trigger via your existing CI).

The first run auto-creates `.fern/replay.lock`. Replay tracks customer commits to generated files from this point on.

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`

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:**

```yaml title="generators.yml" {3}
github:
  repository: customer/python-sdk
  mode: release   # or mode: push
```

Re-add the credentials you removed in Phase 1.

* Delete `.fern/replay.lock`
* Delete `.fern/replay.yml`
* Delete the `fern-generation-base` tag if it exists

## Troubleshooting

| Command                                                                                    | Effect                                                                                                                                |
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------- |
| [`fern generate --no-replay`](/learn/cli-api-reference/cli-reference/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/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.