ai newsFr., 30. Okt.
EilmeldungKI-Radar #5: Kurz vor AGI – warum gehen dann so viele? Die neuen KI-Labs und ihre Wetten

Shipping AI-assisted migrations without a big-bang PR

I asked an agent once to “migrate our data-fetching layer to the new client.” It came back with a 90-file PR, green CI, and a cheerful summary. I still couldn’t ship it. Reviewers bounced it. Rollback was a fantasy. Two weeks later we finished the same migration in six small PRs and nobody hated us

dev.toFreitag 16:36 · 30.10.5 Min. Lesezeit
Bild: dev.to

I asked an agent once to “migrate our data-fetching layer to the new client.” It came back with a 90-file PR, green CI, and a cheerful summary. I still couldn’t ship it. Reviewers bounced it. Rollback was a fantasy. Two weeks later we finished the same migration in six small PRs and nobody hated us.

AI is great at the mechanical parts of a migration. It is terrible at deciding how much to change in one go. Big-bang PRs feel fast because the agent can rewrite overnight. They are slow in calendar time because humans cannot review, stage, or revert them safely. Here’s how I run AI-assisted migrations so each slice is mergeable.

A migration PR is not a refactor. You are changing a contract: old API → new API, old store → new store, class components → hooks, CSS modules → something else. The failure modes are different:

Green unit tests only prove the files the agent touched still pass their suites. They do not prove the migration is safe to flip for every route.

I never prompt “migrate everything to X.” I pick a surface: one route group, one feature module, one package boundary. Example:

## Migration slice: settings routes only Scope: `src/app/settings/**` and colocated tests/fixtures Goal: replace `legacyFetch` with `apiClient` for settings pages Out of scope: billing, onboarding, shared hooks used outside settings Stop if a shared helper needs a signature change — list it, don’t chase it Enter fullscreen mode Exit fullscreen mode That stop rule is the whole game. Migrations explode when the agent follows an import into shared land and “fixes” the shared helper for everyone in the same PR.

A useful heuristic: one PR should be reviewable in under fifteen minutes, and should have a clear “if this breaks, revert this PR” story. If the diff needs a guided tour, it’s too big.

I reuse the same three gates I use for agent-driven refactors:

Order matters. I usually land the new client / adapter behind a flag or unused export first (tiny PR), then migrate one surface to call it (small PR), then delete the old path when the last surface is gone (delete PR). Agents love to do all three. I don’t let them.

Before the agent touches production code, I ask it to freeze the old behavior with fixtures or golden snapshots the new path must match.

// golden: settings profile payload shape (capture once from staging) export const settingsProfileGolden = { id: " u_123 ", displayName: " Ada ", prefs: { theme: " system ", digest: " weekly " }, } as const; Enter fullscreen mode Exit fullscreen mode Then the migration PR gets a dual assertion:

it ( " matches legacy settings profile contract ", async () => { const fromLegacy = await legacyFetchSettingsProfile ( userId ); const fromNew = await apiClient. settings. profile ( userId ); expect ( fromNew ). toEqual ( fromLegacy ); expect ( fromNew ). toMatchObject ( settingsProfileGolden ); }); Enter fullscreen mode Exit fullscreen mode If the new client “improves” the shape (renames a field, drops a null), the golden fails on purpose. That’s the point. Migrations should not silently change contracts unless the PR title says so and the changelog mentions it.

For UI migrations, I do the same with screenshot or DOM snapshots on the critical screens of that surface only — not the whole app. Whole-app visual baselines are how you get 400 flaky diffs and merge with eyes closed.

For anything with side effects (writes, analytics, billing-adjacent), I want a dual-write or flag window, not a hard cutover in the same PR as the rewrite.

1. Add `useNewSettingsClient` flag (default off) — no call-site changes yet 2. Behind the flag, call apiClient; else legacyFetch 3. Log mismatches for 48h on staging (read path) before enabling for one cohort 4. Separate PR to default the flag on for settings only 5. Separate PR to delete legacy path once flag is 100% and mismatch log is empty Enter fullscreen mode Exit fullscreen mode The agent can implement step 1–2 in one PR if the diff stays small. Steps 3–5 stay human-scheduled. “AI finished the migration” is not the same as “we flipped traffic.”

If dual-write is too heavy (pure read UI, no durable state), a flag still buys you a one-line rollback without reverting a giant commit.

Before I merge a migration slice, I write the rollback out loud:

If the honest answer is “we’d have to revert six stacked PRs and redeploy shared types,” the slice was wrong. Split again.

I keep that checklist in the PR template. The agent doesn’t get to mark it done for me.

You are migrating ONE surface. Do not finish the whole migration. Surface: src/features/settings/ ** Replace: legacyFetch(path) → apiClient.request(path) for settings modules only Rules: - Do not edit files outside the surface + their colocated tests - Do not change apiClient’s public types - Do not delete legacyFetch - Do not rename symbols unrelated to the fetch swap - Keep existing test names; update implementations to hit apiClient via the test double - Stop and list paths if a shared helper under src/lib/ ** must change When done: 1.

Summarize files touched (bullet list) 2. Paste test output for the settings package 3. Propose the NEXT surface only — do not start it Enter fullscreen mode Exit fullscreen mode That last line matters.

Agents love to keep going. Your job is to end the turn.

Done is not “the agent said migration complete.” Done looks like:

AI shortens the typing. It does not shorten the need for staged cutovers. If you let it ship a big-bang PR, you didn’t save time — you borrowed it from the next outage.

Slice the surface. Freeze the contract. Flag the cutover. Keep the delete for last. Then let the agent type.

Quelle: dev.to (dev.to). Original öffnen