Przejdź do głównej zawartości

Migracje baz danych i kodu z Claude Code

Migracja z Claude Code to ciąg faz wdrażanych osobno (expand, backfill, przełączenie odczytów i contract przy schemacie; codemod, poprawki agenta i weryfikacja przy aktualizacji frameworka), które Claude Code planuje, pisze i testuje na jednorazowej bazie albo gałęzi. Najważniejsze zastrzeżenie: każda faza trafia na produkcję dopiero wtedy, gdy przejdzie bramkę, a stosuje ją CI, nie agent.

Ta strona jest dla programistów, którzy prowadzą migrację w terminalu, i dla tech leadów, którzy ją zatwierdzają. Tabela users ma 12 milionów wierszy, a kolumna name ma się stać kolumnami first_name i last_name. Express 4 ma się stać Expressem 5 w 180 plikach z trasami. Operator płatności wyłącza API v1 z końcem kwartału. Claude Code napisze wszystkie pliki do tych trzech zadań w jedno popołudnie, a jeden z nich nadal może położyć produkcję.

  • Konfigurację sesji, która trzyma agenta przy bazie roboczej i na gałęzi, którą możesz wyrzucić.
  • Inwentaryzację w trybie planu, zbudowaną przez subagentów, która pokazuje zasięg zmian, zanim zmieni się jakikolwiek plik.
  • Prompty do skopiowania: zmiana schematu metodą expand-contract, wznawialny backfill, aktualizacja frameworka, aktualizacja ORM i przejście na nową wersję API za flagą.
  • Bramkę dla każdego rodzaju migracji, dzięki której sprawdzasz dowody, a nie każdą linię napisaną przez agenta.
  • Sposoby, w jakie migracje psują się właśnie w Claude Code, łącznie z tym, czego /rewind nie cofnie, wraz z wyjściem z każdej sytuacji.

Przebieg jest ten sam dla każdej migracji: inwentaryzacja, plan w fazach, wykonanie jednej fazy, dowód, dopiero potem wdrożenie. Zmienia się narzędzie, które wykonuje główną pracę, i dowód, który potwierdza fazę.

MigracjaGłówną pracę wykonujeDowód, że faza jest bezpiecznaSzczegóły
Zmiana schematu żywej tabeliClaude Code pisze pliki faz, narzędzie do migracji je stosujeLinter blokad, up-down-up na bazie roboczej, zapytanie o zgodność zwracające 0Wzorce migracji baz danych
Nowa główna wersja frameworka lub bibliotekiNajpierw codemod dostawcy, potem Claude Code dla tego, co pominąłSprawdzenie typów, pełny zestaw testów, grep, który nie znajduje starego APIOperacje wsadowe
Nowa główna wersja ORMClaude Code, według przewodnika aktualizacji dostawcySQL generowany przez ORM, porównany przed i poWzorce ORM
Nowa wersja zewnętrznego APIClaude Code, za flagą funkcjiTesty kontraktowe na nagranych odpowiedziach v2, potem porównanie w trybie cieniaWdrażanie stopniowe

Przepisanie całego systemu (zmiana języka, rozbicie monolitu) to program, nie migracja. Potrzebuje testów charakteryzujących i planu wycinków; zobacz modernizację starszego kodu z agentami.

Cztery kroki zapewniają, że migrację da się cofnąć. Wykonaj je w tej kolejności, bo każdy zakłada poprzedni.

  1. Pracuj w worktree. claude -w users-name-split (--worktree w claude --help 2.1.283) tworzy worktree gita i uruchamia w nim sesję, więc nieudana próba to katalog do usunięcia.

  2. Skieruj powłokę na bazę roboczą. Claude Code uruchamia polecenia z twoim środowiskiem. Od v2.1.283 na kanale latest tryb auto jest domyślnym trybem uprawnień w interaktywnych sesjach terminalowych na obsługiwanych przez niego modelach, więc większość poleceń zatwierdza klasyfikator, a nie ty. Jeśli DATABASE_URL wskazuje staging, jedno migrate deploy tam dotrze. Uruchom jednorazowego Postgresa i wyeksportuj jego URL, zanim wpiszesz claude. Praca z bazami danych w Claude Code zawiera polecenie kontenera, reguły deny dla twojego narzędzia do migracji i serwer Postgres MCP Pro tylko do odczytu, przez który zajrzysz do stagingu:

    Okno terminala
    # Terminal, katalog główny repozytorium: Postgres MCP Pro tylko do odczytu dla stagingu
    claude mcp add -s project postgres -e 'DATABASE_URI=${STAGING_DATABASE_URL}' \
    -- uvx postgres-mcp --access-mode=restricted
  3. Zrób commit bazowy. Checkpointy i /rewind przywracają tylko zmiany zrobione narzędziami edycji plików Claude’a. Codemod, sed czy mv uruchomione przez Bash nie są śledzone. Commit przed każdą fazą to twoje prawdziwe „cofnij”.

  4. Zapisz zasady migracji w CLAUDE.md. Czyta je każda sesja, każdy subagent i każdy worker /batch. Gdzie umieścić plik, opisuje system pamięci.

    ## Migrations
    - Never edit a migration file that has been applied anywhere. Write a new one.
    - One phase per migration file: expand, backfill, contract are separate files and separate deploys.
    - Every file starts with SET lock_timeout = '5s' and names the lock each statement takes.
    - The only database you may touch is $DATABASE_URL (local scratch). Never run migrate deploy.
    - Do not edit tests to make a migration pass. Report the failing test instead.

Żeby pierwsza zasada była mechanizmem, a nie prośbą, dodaj hook PreToolUse, który odrzuca Edit i Write w katalogach z zastosowanymi migracjami. Hooki Claude Code zawierają przetestowany skrypt protect-paths.sh do przerobienia.

Tryb planu pozwala Claude Code czytać i proponować bez edycji. Naciskaj Shift+Tab, aż wskaźnik pokaże tryb planu, albo uruchom claude --permission-mode plan. Poproś o inwentaryzację przez równoległych subagentów Explore: każdy przeszukuje jeden obszar i zwraca podsumowanie, więc główna sesja zachowuje kontekst na plan.

MIGRATION.md staje się kontraktem na resztę pracy. Sprawdź w nim trzy rzeczy: backfill następuje dopiero, gdy każda instancja zapisuje do starej i nowej kolumny, faza contract to osobne wydanie, a każda faza ma nazwany sposób wycofania. Zatwierdź plan i uruchamiaj każdą fazę jako osobne zadanie. Gdy kontekst sesji się zapełni, wpisz /clear i zacznij kolejną fazę od „Read MIGRATION.md and do phase 3”.

Expand-contract nigdy nie zmienia kolumny w miejscu. Dodajesz nowe kolumny, zapisujesz do starej i nowych, kopiujesz stare dane, przełączasz odczyty, a starą kolumnę usuwasz wydanie później. Stara i nowa wersja aplikacji działają na każdym etapie, więc wycofanie między fazami to ponowne wdrożenie, a nie odtwarzanie z kopii.

Zanim zrobisz cokolwiek dalej, udowodnij plik expand na bazie roboczej: zastosuj go, zastosuj plik down, porównaj wynik pg_dump --schema-only przed i po, i zastosuj go ponownie. Skrypt up-down-up znajdziesz w pracy z bazami danych w Claude Code. Pusty diff schematu dowodzi, że wycofanie działa, i nikt nie musi czytać SQL. Następnie sprawdź plik pod kątem groźnych blokad Squawkiem, linterem migracji PostgreSQL; przy błędzie kończy się niezerowym kodem, więc ta sama linia działa jako krok CI:

Okno terminala
# Terminal albo CI: lintowanie każdego pliku migracji dodanego na tej gałęzi
FILES=$(git diff --name-only --diff-filter=A origin/main -- 'db/migrations/*.sql')
[ -z "$FILES" ] || npx squawk-cli@2.66.0 $FILES

Jeden UPDATE na 12 milionach wierszy trzyma blokady przez jedną długą transakcję i powoduje rozrost tabeli (bloat). Poproś o zadanie wsadowe, które da się przerwać i wznowić.

Kolejność jest tu wszystkim. Jeśli backfill ruszy, zanim każda instancja zacznie zapisywać do starej i nowej kolumny, stara instancja może zmienić name już po tym, jak backfill minął ten wiersz, i nowe kolumny zdezaktualizują się bez żadnego błędu. Blokady zakładane przez każde polecenie SQL, bramkę z linterem Squawk i wycofanie każdej fazy opisują wzorce migracji baz danych.

Codemod dostawcy jest deterministyczny i tani, więc uruchom go, zanim wydasz tokeny. Potem Claude Code zajmuje się tym, czego codemod nie obejmuje, i czyta przewodnik migracji dostawcy zamiast polegać na własnej pamięci o API.

Przy przejściu z Expressa 4 na 5 pakiet zespołu Expressa @expressjs/codemod (0.0.5) ma cztery transformacje: magiczne przekierowanie "back", nazwy metod w liczbie mnogiej, przestarzałe sygnatury z v4 oraz req.param(). Pakiet jest oznaczony w npm jako przestarzały (sprawdzone 2026-09-26) i odsyła do codemodów Expressa w rejestrze codemod.com (zakres expressjs). Ta strona zostaje przy pakiecie z npm, bo nadal działa i obejmuje te cztery transformacje; rejestr traktuj jako miejsce na nowsze przepisy. Jego polecenie upgrade nie ma trybu podglądu, więc uruchom go na czystym drzewie i traktuj git diff jako podgląd; git restore . cofa zmiany:

Okno terminala
# Terminal, w worktree, po commicie bazowym
npx @expressjs/codemod upgrade src
git add -A && git commit -m "Express 5: apply official codemod"

Typowa luka to ścieżki tras: Express 5 parsuje je przez path-to-regexp 8 (za pośrednictwem router 2), więc składnia symboli wieloznacznych i opcjonalnych segmentów wymaga zmian, których codemod nie robi.

Gdy zmiana obejmuje setki plików w wielu katalogach, /batch dzieli ją na 5 do 30 jednostek, każdą we własnym worktree i z własnym pull requestem. Przekaż mu przepis weryfikacji z kroków 3 i 4 powyżej; prompt i kolejność scalania opisują operacje wsadowe.

Aktualizacja ORM może przejść wszystkie testy i mimo to zmienić zachowanie: inna domyślna kolejność, inna obsługa NULL w filtrze albo nowa postać zapytania, która omija indeks. Sprawdzanie typów tego nie zobaczy. SQL tak.

Recenzent czyta diff SQL. Kilka zmienionych zapytań, każde z wyjaśnieniem, to recenzja całej aktualizacji; zmiany w miejscach wywołań pokrywają sprawdzanie typów i testy.

Zmiana wersji API dotyczy kodu, którego drugiej strony nie kontrolujesz, więc najpierw wdróż ją jako ukrytą (dark launch). Nagraj prawdziwe odpowiedzi v2 w sandboksie dostawcy, zbuduj na nich testy kontraktowe i przełączaj ruch flagą.

Włącz tryb cienia na produkcji dla wywołań tylko do odczytu, poczekaj, aż dziennik różnic spadnie do zera, a potem przenoś ruch zapisu flagą, krok po kroku. Rozłożenie wdrożenia na etapy i automatyczne wycofanie opisuje wdrażanie stopniowe.

Skąd wiesz, że migracja jest poprawna, bez czytania każdej linii?

Dział zatytułowany „Skąd wiesz, że migracja jest poprawna, bez czytania każdej linii?”

Każda faza zostawia dowody. Recenzent czyta dowody, a uwagę linia po linii poświęca tylko tam, gdzie żadna bramka nie oceni wyniku.

Wynik fazyBramka, która go dowodziKto zatwierdza
Plan w MIGRATION.mdKażda faza ma nazwane wdrożenie, bramkę i wycofanie; backfill następuje po zapisie do starej i nowej kolumnyTech lead, przed zatwierdzeniem trybu planu
Plik expand lub contractSquawk przechodzi; up, down, up na bazie roboczej z pustym diffem schematu; CI to powtarzaWłaściciel schematu, przez CODEOWNERS na katalogu migracji
BackfillZapytanie o zgodność zwraca 0 na danych z przypadkami brzegowymi, potem na produkcji przez rolę tylko do odczytuWłaściciel migracji, z oboma wynikami w pull requeście
Aktualizacja frameworkaSprawdzenie typów, pełny zestaw testów i grep za usuniętym API, który nic nie zwracaCI
Aktualizacja ORMDiff SQL przed i po, każde zmienione zapytanie wyjaśnioneRecenzent pull requesta
Nowa wersja APITesty kontraktowe na nagranych odpowiedziach; dziennik różnic trybu cienia na zerzeWłaściciel usługi, zanim flaga się przesunie

Dwie rzeczy zawsze czyta człowiek: fazę contract, która usuwa dane, i każdy krok oznaczony w planie jako nieodwracalny. Migracje należą do nielicznych klas zmian, przy których takie czytanie nadal się opłaca, co wyjaśnia czytanie dowodów zamiast kodu. Wszystko inne trafia do pakietu dowodów pull requesta.

Żeby powtórzyć kontrolę w CI bez człowieka, uruchom przegląd nowych plików migracji tylko do odczytu przez claude -p. Ten tryb startuje jako Manual i nie może odpowiadać na pytania o uprawnienia, więc --allowedTools to cała jego powierzchnia narzędzi, a --json-schema zamienia wynik w plik, który sprawdzi kolejny krok:

Okno terminala
# Zadanie CI na zaufanej gałęzi (push do main albo pull request z tego samego repozytorium)
FILES=$(git diff --name-only --diff-filter=A origin/main -- 'db/migrations/*.sql')
claude -p "Review these new migration files: $FILES. For every statement, name the lock it takes, whether the paired down migration reverses it, and whether it can lose data." \
--bare --setting-sources "" --strict-mcp-config \
--allowedTools "Read Grep Glob" \
--output-format json \
--json-schema "$(cat .github/migration-review.schema.json)" \
> migration-review.json
jq -e '.subtype == "success" and .is_error == false' migration-review.json > /dev/null

Plik schematu opisuje tablicę obiektów {file, statement, lock, reversible, loses_data}; kolejny krok kończy zadanie błędem, gdy któryś wpis ma loses_data: true bez etykiety zatwierdzenia przez człowieka. Linia jq -e kończy zadanie błędem, gdy przebieg się nie dokończył, więc przerwany albo ucięty limitem przegląd nigdy nie przejdzie jako pusty. --bare --setting-sources "" --strict-mcp-config nie pozwala hookom, ustawieniom ani .mcp.json z gałęzi załadować się obok ANTHROPIC_API_KEY; --bare uwierzytelnia się wyłącznie tym kluczem. W pull requeście workflow, plik schematu i kolejny krok pochodzą z samego pull requesta, więc obejmij .github/ wpisem w CODEOWNERS z wymaganym przeglądem, inaczej pull request może osłabić kontrolę w tym samym diffie. Uruchamiaj to tylko na zaufanych gałęziach i nigdy na kodzie z forka w zdarzeniu pull_request_target: hooki i serwery MCP z katalogu .claude/ autora działałyby z sekretami zadania. Bezpieczny workflow na ten przypadek opisuje CI/CD z Claude Code.

/rewind nie cofnął codemodu. Checkpointy śledzą tylko zmiany zrobione narzędziami edycji plików Claude’a, a nie pliki zmienione poleceniem Bash. Wyjście: git, czyli git restore . do commita bazowego albo git reset --hard w worktree. Zapobieganie: commit przed każdą fazą.

Backfill wyczerpuje pamięć albo spowalnia bazę główną. Skrypt ładował wiersze do pamięci albo działał bez przerw. Zatrzymaj go; dzięki filtrowi first_name IS NULL nic nie przepada. Poproś Claude Code o mniejsze partie, stronicowanie keyset i logowanie ostatniego id, a potem wznów przez --start-after.

Trigger synchronizujący wywołuje się rekurencyjnie albo podwaja czas zapisu. Trigger BEFORE na poziomie wiersza, który tylko przypisuje kolumny NEW, nie wywołuje się rekurencyjnie. Jeśli agent napisał trigger AFTER, który aktualizuje własną tabelę, odpala się on ponownie: przepisz go na trigger BEFORE i zostaw zabezpieczenie pg_trigger_depth(). W obu przypadkach zmierz czas wstawiania na bazie roboczej przed plikiem expand i po nim. Jeśli koszt jest za wysoki, usuń trigger i polegaj na zapisie do obu kolumn w aplikacji, co oznacza, że backfill musi poczekać, aż nie będzie żadnej starej instancji.

Zapytanie o zgodność nie zwraca zera po backfillu. Instancja w starej wersji zapisała tylko name albo funkcja rozbijająca daje niespójne wyniki na przypadkach brzegowych. Upewnij się, że każda instancja działa w wersji z zapisem do obu kolumn, uruchom partię naprawczą z filtrem na niezgodność zamiast IS NULL i przejrzyj tabelę do weryfikacji. Nie wdrażaj fazy contract, dopóki wynik nie wynosi 0.

Migracja przechodzi na danych testowych i pada na produkcji. Produkcja ma wartości, których nie ma w danych startowych: NULL tam, gdzie schemat na to pozwala, puste napisy, bardzo długie napisy, nietypowy Unicode. Poproś Claude Code o zapytanie wstępne, które policzy każdy z tych przypadków w zmienianych kolumnach, uruchom je przez rolę tylko do odczytu i dodaj te przypadki do danych startowych.

Claude Code zmienił zastosowaną migrację albo test, żeby dojść do zielonego. Sesja miała prawo zapisu do obu. Przywróć plik z gita, powtórz zasadę i zamień ją w mechanizm: hook protect-paths.sh i reguła deny dla Edit na plikach testów. Zobacz ochronę wyroczni testowej.

Długa aktualizacja gubi wątek w połowie. Kontekst zapełnił się wynikami testów, a wczesne decyzje zostały streszczone. Trzymaj stan w MIGRATION.md, poproś Claude Code o odhaczanie w nim każdej fazy i zaczynaj każdą fazę w nowej sesji.

Ta sama praca w innych narzędziach: modernizacja kodu w Codex, praca z bazami danych w Codex i migracje w Cursorze.