Przejdź do głównej zawartości

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.

  • 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

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.

ObjawWzorzecDowody dla agentaDowód, że poprawka działa
Czasem zawodzi, bez wyraźnego wyzwalacza1. Instrumentacja i korelacjaUstrukturyzowane logi ze 100 lub więcej odtworzonych przebiegówTa sama pętla pokazuje zero błędów
Jeden wyjątek ze śladem stosu2. Ślad stosu → celowana poprawkaPełny ślad, dane zdarzenia, wydanieTest zbudowany ze zdarzenia najpierw nie przechodzi, potem przechodzi
Pamięć rośnie aż do restartu procesu3. Różnica migawek stertyDwie migawki przy tym samym obciążeniu, główne obiekty zatrzymująceSkrypt obciążeniowy (soak: ta sama operacja powtórzona tysiące razy z limitem wzrostu sterty) przechodzi
Duplikaty lub zgubione zapisy pod obciążeniem4. Test współbieżności, który nie przechodziOperacja, współdzielony stan, zaobserwowany duplikatTest współbieżny nie przechodzi przed poprawką, przechodzi po niej
Wolno lub błędnie tylko między usługami5. Oś czasu według ID żądaniaLogi lub spany z każdego przeskoku, z ID żądaniaLuki 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.

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.

Okno terminala
# Terminal, katalog główny repozytorium
npx skills add obra/superpowers --skill systematic-debugging -a claude-code -y
claude mcp add -s project --transport http sentry https://mcp.sentry.dev/mcp
claude mcp login sentry

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.

  1. Najpierw zatwierdź lub odłóż (stash) swoje zmiany, żeby git diff pokazywał tylko to, co doda agent. Instrumentacja jest tymczasowa; na końcu ją cofniesz.

  2. Poproś wyłącznie o logowanie, ograniczone do podejrzanej ścieżki. Podaj objaw, częstość i to, co już wykluczyłeś.

  3. 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ńca
    for i in $(seq 1 100); do
    echo "RUN $i START" >> debug.log
    npx vitest run tests/auth/validate.test.ts >> debug.log 2>&1 \
    && echo "RUN $i PASSED" >> debug.log || echo "RUN $i FAILED" >> debug.log
    done
    grep -c "^RUN [0-9]* FAILED" debug.log

    Jeś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.

  4. 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 odczytu
    grep -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."

    Przydatna odpowiedź wskazuje przyczynę, którą da się przetestować, na przykład: „każdy błąd ma exp mniej 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.

  5. 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.

  6. Usuń instrumentację. Poproś agenta o cofnięcie każdej linii [AUTH-DEBUG] i potwierdź, że git grep AUTH-DEBUG nic nie zwraca. Trwałe logowanie należy do wzorców logowania, a nie do pozostałości po polowaniu na błąd.

Ś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.

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”.

  1. Uruchom usługę z migawkami na sygnał. --heapsnapshot-signal zapisuje plik .heapsnapshot w katalogu roboczym za każdym razem, gdy proces dostanie sygnał:

    Okno terminala
    # Terminal
    node --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 2

    Zapis 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.

  2. 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.

  3. Daj agentowi różnicę i poproś o ścieżkę zatrzymania.

  4. 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.

Okno terminala
# 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.log
done
grep -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ódCo dowodziKto go dostarcza
Reprodukcja (test lub pętla) i jej wynik przed poprawkąAgent odtworzył zgłoszony błąd, a nie jego sąsiadaAgent, sprawdza programista
Ta sama reprodukcja po poprawce, z liczbą przebiegówPrzyczyna zniknęła, a nie tylko występuje rzadziejAgent
Pełny zestaw testów, sprawdzanie typów i lint na zielono w CINic innego się nie zepsułoCI
Diagnoza w dwóch zdaniach: przyczyna i dowód na niąPoprawka trafia w przyczynę, a nie w objawProgramista
Przegląd diffu przez drugiego agenta względem tej diagnozySkutki uboczne, które zmęczony autor przeoczyOsobny przebieg przeglądu
Wskaźnik błędów lub metryka po wdrożeniu, przez jeden szczyt ruchuPoprawka trzyma się na produkcjiProgramista, 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.