Wzorce debugowania
Wzorce debugowania z pomocą AI to pięć receptur dla Claude Code, Codeksa i Cursora, w których dowody zbiera się przed poprawką: instrumentacja i korelacja sporadycznych błędów, celowana poprawka ze śladu stosu, wycieki pamięci z migawek sterty, wyścigi odtworzone testem oraz oś czasu między usługami według ID żądania. Każda kończy się sprawdzeniem, które przed poprawką nie przechodzi, a po niej przechodzi.
Ta strona jest dla programistów, którzy oddają błędy agentowi, i dla tech leadów, którzy decydują, jakie dowody musi zawierać pull request z poprawką. Sytuacja: mniej więcej co trzecie logowanie na produkcji kończy się błędem, ślad stosu wskazuje linię, która wygląda poprawnie, a błąd znika, gdy podłączasz debugger. Poproszony o „naprawę błędu autoryzacji” agent zwraca pewną siebie hipotezę i diff, po którym objaw cichnie. Poniższe wzorce każą agentowi najpierw zebrać dowody, a poprawkę udowodnić sprawdzeniem, które możesz uruchomić ponownie.
Co dają ci te wzorce debugowania
Dział zatytułowany „Co dają ci te wzorce debugowania”- Tabelę „objaw → wzorzec”, która mówi, jakie dowody zebrać, zanim agent cokolwiek zmieni
- Siedem gotowych promptów: instrumentacja, korelacja logów, diagnoza śladu stosu, analiza różnicy migawek sterty, odtworzenie wyścigu, oś czasu między usługami i przegląd poprawki
- Dokładne polecenia, którymi przekażesz logi do Claude Code i Codeksa bez sesji interaktywnej
- Listę dowodów, dzięki której recenzent zatwierdzi poprawkę bez czytania każdej linii diffu
- Sposoby, na jakie debugowanie z agentem zawodzi, i wyjście z każdego z nich
Który wzorzec debugowania pasuje do twojego błędu?
Dział zatytułowany „Który wzorzec debugowania pasuje do twojego błędu?”Zacznij od objawu. Każdy wiersz podaje dowody, których potrzebuje agent, i sprawdzenie, które dowodzi poprawki, bo poprawka bez sprawdzenia, które najpierw nie przechodziło, to wciąż zgadywanie.
| Objaw | Wzorzec | Dowody dla agenta | Dowód, że poprawka działa |
|---|---|---|---|
| Czasem zawodzi, bez wyraźnego wyzwalacza | 1. Instrumentacja i korelacja | Ustrukturyzowane logi ze 100 lub więcej odtworzonych przebiegów | Ta sama pętla pokazuje zero błędów |
| Jeden wyjątek ze śladem stosu | 2. Ślad stosu → celowana poprawka | Pełny ślad, dane zdarzenia, wydanie | Test zbudowany ze zdarzenia najpierw nie przechodzi, potem przechodzi |
| Pamięć rośnie aż do restartu procesu | 3. Różnica migawek sterty | Dwie migawki przy tym samym obciążeniu, główne obiekty zatrzymujące | Skrypt obciążeniowy (soak: ta sama operacja powtórzona tysiące razy z limitem wzrostu sterty) przechodzi |
| Duplikaty lub zgubione zapisy pod obciążeniem | 4. Test współbieżności, który nie przechodzi | Operacja, współdzielony stan, zaobserwowany duplikat | Test współbieżny nie przechodzi przed poprawką, przechodzi po niej |
| Wolno lub błędnie tylko między usługami | 5. Oś czasu według ID żądania | Logi lub spany z każdego przeskoku, z ID żądania | Luki nie ma w nowej osi czasu ani w nowym śladzie |
Deterministyczny błąd z jasną reprodukcją nie potrzebuje żadnego z tych wzorców: napisz test, który nie przechodzi, i daj agentowi go naprawić, jak w programowaniu sterowanym testami z AI.
Przygotuj agenta przed pierwszym błędem
Dział zatytułowany „Przygotuj agenta przed pierwszym błędem”Dwa dodatki obniżają koszt każdego z poniższych wzorców. Skill do debugowania daje agentowi metodę (najpierw dowody, jedna hipoteza naraz, test przed poprawką), więc nie powtarzasz jej w każdym prompcie. Serwer MCP narzędzia do śledzenia błędów, podłączony tylko do odczytu, pozwala agentowi pobrać prawdziwy ślad stosu i zdarzenie zamiast twojej parafrazy. Obie instalacje, razem z pułapkami, opisują strony o skillach do testowania i debugowania oraz o serwerach MCP do obserwowalności.
# Terminal, katalog główny repozytoriumnpx skills add obra/superpowers --skill systematic-debugging -a claude-code -yclaude mcp add -s project --transport http sentry https://mcp.sentry.dev/mcpclaude mcp login sentry# Terminal, katalog główny repozytoriumnpx skills add obra/superpowers --skill systematic-debugging -a codex -ycodex mcp add sentry --url https://mcp.sentry.dev/mcpcodex mcp login sentry# Terminal, katalog główny repozytoriumnpx skills add obra/superpowers --skill systematic-debugging -a cursor -yNastępnie dodaj serwer do .cursor/mcp.json i dokończ logowanie OAuth, gdy Cursor o nie poprosi:
{ "mcpServers": { "sentry": { "url": "https://mcp.sentry.dev/mcp" } } }Na ekranie OAuth Sentry zostaw Triage Issues i Manage Projects & Teams odznaczone: agent może wtedy czytać zgłoszenia i zdarzenia, ale nie może ich zamykać ani przypisywać. Jeśli używasz innego narzędzia do śledzenia błędów, wzorce działają tak samo z wklejonym śladem stosu.
Wzorzec 1: zinstrumentuj i skoreluj sporadyczny błąd
Dział zatytułowany „Wzorzec 1: zinstrumentuj i skoreluj sporadyczny błąd”Sporadyczny błąd wymaga danych z wielu przebiegów, a nie dłuższego wpatrywania się w jeden. Agent dodaje wąskie, ustrukturyzowane logowanie, ty odtwarzasz błąd wiele razy, a agent porównuje przebiegi nieudane z udanymi, zanim cokolwiek zaproponuje.
-
Najpierw zatwierdź lub odłóż (
stash) swoje zmiany, żebygit diffpokazywał tylko to, co doda agent. Instrumentacja jest tymczasowa; na końcu ją cofniesz. -
Poproś wyłącznie o logowanie, ograniczone do podejrzanej ścieżki. Podaj objaw, częstość i to, co już wykluczyłeś.
-
Odtwórz błąd wiele razy i zapisz wynik. Jeden udany przebieg niczego nie dowodzi przy błędzie sporadycznym. Uruchom w pętli najwęższy test, który przechodzi przez tę ścieżkę:
Okno terminala # Terminal. 100 przebiegów jednego pliku testów; każdy przebieg ma w logu znacznik początku i końcafor i in $(seq 1 100); doecho "RUN $i START" >> debug.lognpx vitest run tests/auth/validate.test.ts >> debug.log 2>&1 \&& echo "RUN $i PASSED" >> debug.log || echo "RUN $i FAILED" >> debug.logdonegrep -c "^RUN [0-9]* FAILED" debug.logJeśli test lokalnie nigdy nie zawodzi, wyzwalacz tkwi w środowisku (rozjazd zegarów między hostami, obciążenie, współdzielony cache). Zbierz wtedy linie
[AUTH-DEBUG]ze stagingu lub produkcji z tego samego okna czasu. -
Poproś o korelację, nie o poprawkę. Przekaż log agentowi w przebiegu tylko do odczytu, żeby nie mógł edytować kodu, gdy rozumuje. Poniższe flagi sprawdziliśmy w Claude Code 2.1.283 i Codex 0.157.1.
Okno terminala # -p działa bez interakcji i wypisuje odpowiedź; tryb planu utrzymuje go tylko do odczytugrep -E "AUTH-DEBUG|^RUN " debug.log | claude -p --permission-mode plan \"Correlate the failing [AUTH-DEBUG] events against the passing ones. What do the failures share that the passes do not? Give confidence and the log lines that support it. No fix yet."Okno terminala # Wejście z potoku jest doklejane do promptu; sandbox tylko do odczytu, bez edycjigrep -E "AUTH-DEBUG|^RUN " debug.log | codex exec -s read-only \"Correlate the failing [AUTH-DEBUG] events against the passing ones. What do the failures share that the passes do not? Give confidence and the log lines that support it. No fix yet."Przełącz się na Plan Mode, dołącz
debug.logdo czatu przez@i wklej poniższy prompt korelacyjny. Plan Mode tworzy plan zamiast edycji, więc ten krok pozostaje tylko do odczytu.Przydatna odpowiedź wskazuje przyczynę, którą da się przetestować, na przykład: „każdy błąd ma
expmniej niż 2 s przed zegarem hosta walidującego i wszystkie pochodzą z hosta B, którego zegar spieszy się o 4 s”. Mglista odpowiedź („możliwe, że to warunek wyścigu”) znaczy, że logowanie nie złapało sygnału; dodaj pole, o które prosi agent, i uruchom pętlę jeszcze raz. -
Zamień przyczynę w test, który nie przechodzi, a potem napraw. Test utrwala warunek (token walidowany przez host, którego zegar spieszy się o 4 s) i musi zawieść na obecnym kodzie. Dopiero wtedy agent dodaje poprawkę, na przykład niewielką tolerancję rozjazdu zegara, a pętla z kroku 3 rusza ponownie. Zero błędów na 100 przebiegów plus nowy test to twój dowód.
-
Usuń instrumentację. Poproś agenta o cofnięcie każdej linii
[AUTH-DEBUG]i potwierdź, żegit grep AUTH-DEBUGnic nie zwraca. Trwałe logowanie należy do wzorców logowania, a nie do pozostałości po polowaniu na błąd.
Wzorzec 2: zamień ślad stosu w celowaną poprawkę
Dział zatytułowany „Wzorzec 2: zamień ślad stosu w celowaną poprawkꔌlad stosu wskazuje plik, linię i łańcuch wywołań, więc to najbogatsze pojedyncze wejście, jakie możesz dać agentowi. Ryzyko leży w poprawce: najszybszy sposób, żeby Cannot read properties of undefined zniknęło, to optional chaining wszędzie, co ukrywa prawdziwy błąd. Każ agentowi sklasyfikować brakującą wartość, zanim dotknie kodu.
Dobra poprawka trzyma oba przypadki osobno:
async createOrder(userId: string, input: OrderInput) { // „Nie powinno się zdarzyć”: kontroler zwalidował userId, więc to błąd wyżej w łańcuchu const user = await this.users.find(userId); if (!user) throw new NotFoundError(`User ${userId} not found`);
// „Czasem się zdarza”: użytkownicy sprzed uruchomienia płatności nie mają rekordu klienta if (!user.billingCustomer) { logger.warn('user without billing customer; creating one', { userId }); user.billingCustomer = await this.billing.createCustomer(user); } return this.orders.create(user, input);}Liczy się test zbudowany z prawdziwego zdarzenia. Jeśli test agenta przechodzi na kodzie bez poprawki, odtworzył coś innego; odeślij agenta do punktu 1.
Ten prompt oraz prompty z wzorców 3 i 4 działają bez zmian w Claude Code, Codeksie i Cursorze. Od narzędzia zależy tylko przekazywanie logów bez sesji interaktywnej we wzorcach 1 i 5, dlatego zakładki mają wyłącznie te kroki.
Wzorzec 3: znajdź wyciek pamięci w migawkach sterty
Dział zatytułowany „Wzorzec 3: znajdź wyciek pamięci w migawkach sterty”Sterta, która rośnie aż do restartu procesu, to problem z zatrzymywaniem obiektów, a zatrzymywanie widać w dwóch migawkach sterty zrobionych przy tym samym obciążeniu. Zbierz dowody narzędziami samego Node’a, a agentowi daj listę obiektów zatrzymujących, nie zdanie „coś cieknie”.
-
Uruchom usługę z migawkami na sygnał.
--heapsnapshot-signalzapisuje plik.heapsnapshotw katalogu roboczym za każdym razem, gdy proces dostanie sygnał:Okno terminala # Terminalnode --heapsnapshot-signal=SIGUSR2 dist/server.js &# obciążenie rozgrzewające, aż sterta się ustabilizuje, potem:kill -USR2 $! # migawka 1# 30 minut tego samego obciążenia, potem:kill -USR2 $! # migawka 2Zapis migawki blokuje pętlę zdarzeń i potrzebuje mniej więcej tyle dodatkowej pamięci, ile zajmuje sama sterta, więc w usłudze bliskiej limitu pamięci może wywołać ten sam restart, którego szukasz. Rób migawki na jednej instancji wyłączonej z load balancera albo na stagingu przy tym samym obciążeniu i zostaw zapas pamięci.
-
Porównaj migawki. Wczytaj obie do Chrome DevTools (karta Memory), wybierz drugą i przełącz widok na Comparison. Posortuj według różnicy rozmiaru i skopiuj 10 pierwszych konstruktorów ze ścieżkami obiektów zatrzymujących.
-
Daj agentowi różnicę i poproś o ścieżkę zatrzymania.
-
Uruchom skrypt obciążeniowy (
soak-leak.mjs) przed poprawką i po niej.global.gc()wymaga flagi--expose-gc:node --expose-gc scripts/soak-leak.mjs. Zwykły skrypt Node’a zdejmuje pytanie, czy procesy robocze twojego runnera testów dziedziczą tę flagę. Najczęstsza przyczyna to nieograniczona kolekcja: listenery, timery albo wpisy cache dodawane przy każdym żądaniu i nigdy nieusuwane. Poprawka to ścieżka zwalniania, którą wywołujący rzeczywiście wywołują, a skrypt obciążeniowy uruchamiany w CI pokazuje, że robią to nadal.
Wzorzec 4: odtwórz warunek wyścigu testem, który nie przechodzi
Dział zatytułowany „Wzorzec 4: odtwórz warunek wyścigu testem, który nie przechodzi”Bez testu, który najpierw nie przechodził, poprawka wyścigu to tylko nadzieja. Każ agentowi napisać test współbieżny, który nie przechodzi na obecnym kodzie, a potem wdrożyć najmniejsze zabezpieczenie, po którym test przejdzie.
Lekcja siedzi w dwóch ostatnich zdaniach promptu. Bez nich najprostsze zabezpieczenie to Map w pamięci z kluczem po ID użytkownika. Taki kod przechodzi test w jednym procesie, a i tak pobiera opłatę dwa razy, gdy dwa kliknięcia trafią do dwóch instancji. Zabezpieczenie, które działa przy wielu instancjach, wygląda tak:
async processPayment(key: string, payment: Payment) { // INSERT ... ON CONFLICT (idempotency_key) DO UPDATE SET status = 'pending' // WHERE payment_attempts.status = 'failed' // na unikalnym indeksie idempotency_key: // klucz zajmie tylko jedna instancja, niezależnie od tego, ile instancji dostanie kliknięcie const claimed = await db.paymentAttempts.insertIfAbsent({ idempotencyKey: key, status: 'pending' }); if (!claimed) throw new ConflictError('Payment already processing');
try { const charge = await gateway.charge(payment, { idempotencyKey: key }); await db.paymentAttempts.update(key, { status: 'done', chargeId: charge.id }); return charge; } catch (err) { // zwolnij klucz, inaczej błąd bramki zostawi go w 'pending' i każda ponowna próba dostanie ConflictError await db.paymentAttempts.update(key, { status: 'failed' }); throw err; }}Gałąź catch jest równie ważna jak samo zajęcie klucza: bez niej jeden timeout bramki blokuje ten klucz na zawsze. insertIfAbsent musi traktować wiersz failed jako ponownie do zajęcia. Zwolnienie klucza jest bezpieczne tylko dlatego, że klucz trafia też do bramki: timeout nie mówi, czy opłata przeszła, a bramka deduplikująca po tym samym kluczu zamienia ponowną próbę w odczyt zamiast drugiego obciążenia. Jeśli twoja bramka nie obsługuje klucza idempotencji, zostaw wiersz w pending i uzgodnij go z bramką, zanim pozwolisz na ponowną próbę. Zestaw testów potrzebuje drugiego przypadku, w którym mock bramki raz rzuca wyjątek, a ponowna próba się udaje.
Uruchom test co najmniej 20 razy w pętli, jak we wzorcu 1. Test współbieżności, który przeszedł raz, niewiele dowodzi.
Wzorzec 5: odtwórz oś czasu między usługami według ID żądania
Dział zatytułowany „Wzorzec 5: odtwórz oś czasu między usługami według ID żądania”Gdy żądanie przechodzi przez kilka usług, żaden pojedynczy log nie wyjaśnia błędu; wyjaśnienie leży w lukach między przeskokami. Nadaj ID żądania na brzegu systemu, zbierz logi z każdego przeskoku i poproś agenta o odtworzenie osi czasu.
# Terminal. Godzina logów z każdego przeskoku (dostosuj do swojej platformy)for app in api-gateway order-service payment-service; do kubectl logs -l app=$app --since=1h --timestamps --tail=-1 | sed "s/ / [$app] /" > $app.logdonegrep -h "req_7f3a2c" *.log | sort | claude -p --permission-mode plan \ "Rebuild the timeline for request req_7f3a2c across these services. Flag the longest gap."sed oznacza każdą linię nazwą usługi zaraz po znaczniku czasu, więc sort nadal układa linie według czasu, a agent widzi, który przeskok wypisał daną linię. --tail=-1 jest ważne: z selektorem etykiet kubectl logs zwraca tylko 10 ostatnich linii z każdego poda, jeśli nie ustawisz --tail, a agent odbudowałby oś czasu z niepełnych danych bez żadnego ostrzeżenia.
Logi produkcyjne zawierają dane użytkowników i ciągi, które użytkownicy kontrolują: najpierw zamaskuj adresy e-mail, tokeny i identyfikatory (na przykład filtrem sed w potoku) i uruchamiaj agenta tylko do odczytu, jak tutaj, bo linie logu mogą zawierać tekst, który brzmi jak polecenia. To samo dotyczy linii [AUTH-DEBUG] zebranych ze stagingu lub produkcji we wzorcu 1.
W Codeksie zamień ostatnie polecenie na codex exec -s read-only z tym samym promptem. W Cursorze dołącz trzy pliki przez @ w Plan Mode.
Oś czasu zamienia „checkout czasem działa wolno” w „payment-service czekał 5 s na połączenie z bazą, bo pulę wyczerpało zadanie wsadowe”. Jeśli twoje usługi eksportują spany OpenTelemetry, daj agentowi ślad zamiast logów tekstowych: prompt działa bez zmian, a struktura rodzic–dziecko usuwa zgadywanie kolejności. Podłączenie samych śladów i alertów opisują wzorce monitorowania i logowania.
Jak udowodnić poprawkę błędu bez czytania każdej linii?
Dział zatytułowany „Jak udowodnić poprawkę błędu bez czytania każdej linii?”Recenzent powinien zatwierdzać pull request z poprawką na podstawie dowodów, a kod czytać tylko tam, gdzie dowodu brakuje albo coś zaskakuje. Wymagaj w opisie pull requesta następujących elementów:
| Dowód | Co dowodzi | Kto go dostarcza |
|---|---|---|
| Reprodukcja (test lub pętla) i jej wynik przed poprawką | Agent odtworzył zgłoszony błąd, a nie jego sąsiada | Agent, sprawdza programista |
| Ta sama reprodukcja po poprawce, z liczbą przebiegów | Przyczyna zniknęła, a nie tylko występuje rzadziej | Agent |
| Pełny zestaw testów, sprawdzanie typów i lint na zielono w CI | Nic innego się nie zepsuło | CI |
| Diagnoza w dwóch zdaniach: przyczyna i dowód na nią | Poprawka trafia w przyczynę, a nie w objaw | Programista |
| Przegląd diffu przez drugiego agenta względem tej diagnozy | Skutki uboczne, które zmęczony autor przeoczy | Osobny przebieg przeglądu |
| Wskaźnik błędów lub metryka po wdrożeniu, przez jeden szczyt ruchu | Poprawka trzyma się na produkcji | Programista, który wdrożył |
Wbudowane narzędzia przeglądu dają ogólny przegląd bez diagnozy: /code-review w sesji Claude Code, codex exec review --uncommitted w terminalu albo Bugbot na pull requeście w Cursorze. W Codeksie własne instrukcje i wskazanie diffu wykluczają się: codex exec review "<prompt>" bez flagi celu przyjmuje twoje instrukcje, ale codex exec review --uncommitted "<prompt>" kończy się błędem „the argument ‘—uncommitted’ cannot be used with ‘[PROMPT]’” (sprawdzone w codex-cli 0.157.1).
Żeby przejrzeć poprawkę względem diagnozy, przekaż diff potokiem do nowej sesji bez interakcji, tylko do odczytu, z poniższym promptem: git diff | claude -p --permission-mode plan "<review prompt>" albo git diff | codex exec -s read-only "<review prompt>" (lub codex exec review "<review prompt>" bez flagi celu). W Cursorze wklej prompt w Plan Mode.
Zatwierdzanie: programista, który prowadził polowanie, odpowiada za dowody; recenzent, przy poprawce incydentu zwykle tech lead, zatwierdza, gdy obecny jest każdy wiersz. Agent nie może edytować testu reprodukującego, żeby przeszedł. Ochrona wyroczni pokazuje, jak uczynić to mechanicznie niemożliwym, zamiast polegać na zaufaniu.
Co idzie nie tak, gdy agent debuguje za ciebie?
Dział zatytułowany „Co idzie nie tak, gdy agent debuguje za ciebie?”Dokąd dalej z debugowaniem
Dział zatytułowany „Dokąd dalej z debugowaniem”- Zacznij od przeglądu sekcji: receptury obsługi błędów.
- Pętle debugowania w konkretnych narzędziach: Claude Code w terminalu, Codex na wszystkich powierzchniach i Plan Mode oraz tryb Agent w Cursorze.
- Każdy błąd produkcyjny najpierw jako test: programowanie sterowane błędami.
- Dalej w tej sekcji: wzorce logowania, czyli ustrukturyzowane logi, których potrzebują te prompty, a potem wzorce odzyskiwania z ponawianiem i łagodną degradacją, gdy już znasz przyczynę.