Ein Bug um 23:47 Uhr. Das Terminal zeigt einen kryptischen Stack-Trace. Du hast ihn
bereits dreimal gelesen — ohne Fortschritt. Genau hier entscheidet sich, ob Claude Code
nur ein weiteres Autocomplete-Tool ist, oder dein bester Debugging-Partner. Dieser Leitfaden
zeigt dir die systematischen Methoden, mit denen du 2026 schneller debuggst als je zuvor.
1 Debugging-Mindset mit KI
Der größte Fehler beim KI-gestützten Debugging: Claude Code wie eine Suchmaschine
behandeln. Ein kurzes "Warum crasht das?" ohne Kontext liefert generische Antworten.
Der Schlüssel liegt im Dialog-basierten Debugging — du gibst Kontext,
Claude Code liefert Hypothesen, du testest, du feedst die Ergebnisse zurück.
Kontext ist alles
Bevor du irgendetwas tippst, sammle: den vollständigen Stack-Trace, den betreffenden
Code-Abschnitt, das erwartete vs. das tatsächliche Verhalten und — wenn möglich —
die letzten Commits, die den Bug eingebracht haben könnten.
Beispiel-Prompt an Claude Code
"Ich debugge einen TypeScript-Fehler in meiner Next.js 14 App (Node 20, App Router).
Das Problem tritt nur in Production auf, nicht lokal. Hier ist der Stack-Trace:
[Stack-Trace einfügen]. Hier ist die betroffene Funktion:
[Code einfügen]. Erwartet: Die API gibt ein valides User-Objekt zurück.
Tatsächlich: undefined wird zurückgegeben. Was sind die wahrscheinlichsten
Ursachen und wie soll ich vorgehen?"
Hypothesen-getriebenes Debugging
Claude Code generiert typischerweise 2–4 Hypothesen, warum ein Bug auftritt.
Deine Aufgabe: jede Hypothese systematisch testen, nicht raten. Das Ergebnis
jedes Tests führst du direkt zurück in den Chat — so verfeinert sich
das Modell mit jeder Runde.
- 1Kontext vollständig beschreiben (Stack-Trace + Code + Umgebung)
- 2Claude Code generiert Hypothesen A, B, C nach Wahrscheinlichkeit
- 3Hypothese A testen (die wahrscheinlichste zuerst)
- 4Ergebnis rückführen: "Hypothese A widerlegt, weil X. Weiter mit B?"
- 5Bei Erfolg: Fix verstehen, nicht nur kopieren
- 6Regression-Test schreiben lassen
Profi-Tipp: Sage Claude Code explizit, was du bereits ausgeschlossen hast.
"Ich habe bereits geprüft: Netzwerk ist erreichbar, ENV-Variable gesetzt,
Dependencies stimmen überein." Das spart 1–2 Iterationen.
Was Claude Code besonders gut kann
| Aufgabe |
Allein |
Mit Claude Code |
| Stack-Trace lesen |
5–15 min |
30 Sekunden |
| Hypothesen generieren |
Erfahrungsabhängig |
Sofort, strukturiert |
| Async-Bugs identifizieren |
Sehr schwierig |
Mit Kontext präzise |
| Regression-Tests schreiben |
Zeitaufwendig |
Automatisch generiert |
| Root-Cause erklären |
Oft unklar |
Mit Erklärung + Fix |
2 Root-Cause-Analyse mit der 5-Why-Methode
Symptoms zu behandeln statt Ursachen zu beheben ist der teuerste Debugging-Fehler.
Ein Crash, der mit einem Try-Catch versteckt wird, kommt als korrupte Datenbank
wieder. Die 5-Why-Methode — ursprünglich aus dem Toyota Produktionssystem —
zwingt zur Tiefe: Du fragst fünfmal "Warum?", bis du die echte Ursache hältst.
User-Prompt
"Meine API gibt für einige User undefined zurück statt des User-Objekts.
Hilf mir, die Root-Cause mit der 5-Why-Methode zu finden."
- W1Warum gibt die API undefined zurück? → getUserById() findet keinen Datensatz.
- W2Warum findet die Query keinen Datensatz? → Die WHERE-Clause filtert mit falschem Datentyp.
- W3Warum ist der Datentyp falsch? → userId kommt als String aus dem JWT, wird aber als Number verglichen.
- W4Warum kein Typ-Casting? → Middleware fehlt, die JWT-Claims normalisiert.
- W5Warum fehlt die Middleware? → Bei der Auth-Migration wurde die Normalisierungsschicht vergessen.
Root Cause: JWT-Claims werden nicht normalisiert → Fix: Middleware hinzufügen + alle JWT-Felder explizit typisieren.
Symptome vs. Ursachen unterscheiden
Claude Code ist besonders nützlich, wenn du beschreibst was du siehst,
nicht was du vermutest. Der Unterschied:
| Symptom (was du siehst) | Mögliche Ursachen (Root Cause) |
| TypeError: Cannot read property of undefined |
Race Condition, fehlender null-Check, falsche Datenstruktur |
| API antwortet mit 500 |
Unbehandelte Exception, DB-Verbindung unterbrochen, fehlendes ENV |
| Performance bricht bei 100+ Usern ein |
N+1-Query, fehlender DB-Index, Memory-Leak, kein Connection-Pooling |
| Build funktioniert lokal, scheitert in CI |
Node-Version-Unterschied, fehlende ENV-Variable, Pfad-Sensitivität |
TypeScript: Typen als Root-Cause-Detektor
// SCHLECHT: any unterdrückt Root-Cause
function getUser(id: any): any {
return db.query(`SELECT * FROM users WHERE id = ${id}`);
}
// GUT: Typen erzwingen Root-Cause-Denken
type UserId = number & { readonly brand: 'UserId' };
async function getUser(id: UserId): Promise<User | null> {
const row = await db.query<UserRow>(
'SELECT * FROM users WHERE id = $1',
[id]
);
return row ?? null; // Explizit: null ist möglich
}
// JWT-Claim normalisieren (die eigentliche Root-Cause-Fix)
function parseUserId(jwtSub: string): UserId {
const id = parseInt(jwtSub, 10);
if (isNaN(id)) throw new Error(`Invalid user ID in JWT: ${jwtSub}`);
return id as UserId;
}
Anti-Pattern: Quick-Fixes wie || {} oder ?? 'fallback'
ohne zu verstehen warum der Wert undefined ist, verschleiern Root-Causes und
produzieren Folge-Bugs. Claude Code warnt dich aktiv davor — hör hin.
3 Reproduzierbare Testfälle erstellen
Ein Bug, den du nicht reproduzieren kannst, ist ein Bug, den du nicht fixen kannst.
Die Kunst liegt im minimalen Reproduktionsbeispiel (MRE) —
dem kleinsten möglichen Code-Stück, das den Fehler zuverlässig auslöst.
Claude Code hilft dir, dieses MRE systematisch zu erstellen.
Prompt
"Ich habe einen Bug in meiner Authentifizierung. Er tritt auf, wenn der User
sich nach einem Token-Refresh anmeldet, aber nur wenn die Session älter als
24h ist. Hilf mir, ein minimales Reproduktionsbeispiel zu erstellen, das ich
ohne Datenbank und ohne externen Auth-Provider reproduzieren kann."
Unit-Test für den Bug schreiben
Der beste Beweis für einen Bug ist ein failing Unit-Test. Claude Code schreibt
diesen Test, sobald du den Bug beschrieben hast — und der Test bleibt als
Regression-Guard bestehen, nachdem der Fix deployed ist.
// Vitest / Jest — Reproduzierbarer Testfall
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { refreshAuthToken, getUserSession } from '../auth/session';
describe('Session nach Token-Refresh', () => {
beforeEach(() => {
vi.useFakeTimers();
});
it('gibt valide Session nach Refresh zurück wenn Token < 24h', async () => {
const token = await refreshAuthToken('valid-refresh-token');
vi.advanceTimersByTime(23 * 60 * 60 * 1000); // 23h
const session = await getUserSession(token);
expect(session).not.toBeNull();
expect(session?.userId).toBeDefined();
});
it('REPRODUZIERT BUG: gibt undefined nach 24h+ Session', async () => {
const token = await refreshAuthToken('valid-refresh-token');
vi.advanceTimersByTime(25 * 60 * 60 * 1000); // 25h — triggert Bug
const session = await getUserSession(token);
// Dieser Test sollte FEHLSCHLAGEN bis der Fix deployed ist
expect(session).not.toBeUndefined(); // [FAILING] Root-Cause: Token-Expiry nicht geprüft
});
it('NACH FIX: Session wird korrekt erneuert nach 24h', async () => {
const token = await refreshAuthToken('valid-refresh-token');
vi.advanceTimersByTime(25 * 60 * 60 * 1000);
const session = await getUserSession(token);
expect(session?.userId).toBe(42); // Nach Fix: wieder valide
});
});
Isolation durch Mocking
Externe Services machen Bugs schwerer reproduzierbar. Claude Code erzeugt
präzise Mocks für Datenbankverbindungen, APIs und Timer — so ist
dein MRE komplett deterministisch.
// Datenbank-Mock für isoliertes Debugging
import { vi } from 'vitest';
const mockDb = {
query: vi.fn().mockImplementation(async (sql: string, params: unknown[]) => {
// Simuliert exakt das Verhalten das den Bug auslöst
if (sql.includes('sessions') && params[1] instanceof Date) {
const age = Date.now() - (params[1] as Date).getTime();
if (age > 24 * 60 * 60 * 1000) return null; // Bug-Trigger
}
return { id: 42, email: 'user@example.com' };
}),
};
vi.mock('../db/connection', () => ({ db: mockDb }));
Claude Code Prompt: "Erstelle mir ein vollständiges Vitest-Setup,
das diesen Bug isoliert reproduziert, ohne echte DB-Verbindung. Schreib auch den
Fix und einen Regression-Test."
4 Stack-Trace-Interpretation
Ein Stack-Trace ist kein Fehler — er ist eine Karte. Jede Zeile beschreibt
einen Schritt auf dem Weg zur Katastrophe, rückwärts gelesen. Claude Code
liest diese Karte in Sekunden und zeigt dir, wo du suchen musst.
TypeScript-Fehler entschlüsseln
TypeError: Cannot read properties of undefined (reading 'map')
at ProductList (./src/components/ProductList.tsx:23:24)
at renderWithHooks (.next/server/chunks/webpack-runtime.js:741:18)
at mountIndeterminateComponent (react-dom.development.js:20974:7)
at beginWork (react-dom.development.js:22787:16)
at HTMLUnknownElement.callCallback (react-dom.development.js:4161:14)
at Object.invokeGuardedCallbackDev (react-dom.development.js:4210:16)
at invokeGuardedCallback (react-dom.development.js:4274:31)
at beginWork$1 (react-dom.development.js:27405:7)
Prompt an Claude Code
"Analysiere diesen Stack-Trace. Was ist die wahrscheinliche Ursache,
welche Zeile ist die relevanteste und welche 3 Dinge soll ich als
erstes überprüfen?"
Claude Code identifiziert hier sofort: Der Fehler liegt in
ProductList.tsx Zeile 23 — ein Array wird per
.map() iteriert, der aber undefined ist.
Ursachen: fehlender Null-Check, API-Response hat andere Struktur als erwartet,
oder Server-Side-Props haben keinen Fallback.
Async Stack-Traces
Async Bugs sind die heimtückischsten: Der Stack-Trace zeigt oft nicht
wo das Problem entstanden ist, sondern wo es aufgetreten ist.
// SCHLECHT: Async-Fehler verschluckt Stack-Trace
const data = await Promise.all([
fetchUser(),
fetchOrders(), // Wenn dies rejiziert, verlierst du den Ursprung
fetchProducts(),
]);
// GUT: Einzeln awaiten mit Labels für Claude Code
const [user, orders, products] = await Promise.allSettled([
fetchUser().then(r => ({ _label: 'user', ...r })),
fetchOrders().then(r => ({ _label: 'orders', ...r })),
fetchProducts().then(r => ({ _label: 'products', ...r })),
]);
// Fehlgeschlagene Promises explizit loggen
[user, orders, products].forEach((result, i) => {
if (result.status === 'rejected') {
console.error(`Promise[${i}] rejected:`, result.reason);
// Jetzt kann Claude Code den Stack-Trace zuordnen
}
});
Node.js Crash-Reports lesen
# Node.js Crash-Report generieren
node --report-uncaught-exception app.js
# Report-Pfad ausgeben (automatisch in CWD)
# Dateiname: report.20260506.114523.42761.0.001.json
# Relevante Sections für Claude Code
# 1. "header.message" — Fehlertyp
# 2. "javascriptStack" — Stack-Trace
# 3. "nativeStack" — C++ Stack (bei Segfaults)
# 4. "environmentVariables" — ENV zur Diagnose
# 5. "libuv" — Event-Loop-Zustand
Browser-Console-Errors haben häufig Source-Map-Probleme in Production.
Kopiere den Fehler + die oberen 5–8 Stack-Frames in Claude Code
und frage spezifisch nach: "In welcher Original-Quelldatei liegt dieser
Fehler wahrscheinlich, wenn die App mit Webpack/Vite gebaut wurde?"
Source-Maps aktivieren: sourceMaps: true in tsconfig
Vite: build.sourcemap: true in vite.config.ts
Error-Boundary für React-Apps implementieren
Sentry oder ähnliches für Production-Stack-Traces
5 Binary Search Debugging
Binary Search Debugging ist die schnellste Methode, um in großen Codebasen
oder langen Git-Historien den genauen Punkt zu finden, an dem ein Bug eingebracht
wurde. Das Prinzip: Halbiere den Suchraum bei jedem Schritt.
git bisect mit Claude Code
git bisect ist das mächtigste Binary-Search-Tool für Entwickler.
Claude Code erklärt dir nicht nur die Befehle — es analysiert auch,
welchen Commit es sich als nächstes ansehen soll.
# Git Bisect Session starten
git bisect start
git bisect bad # Aktueller Stand ist kaputt
git bisect good v2.1.0 # Dieser Tag war noch OK
# Git prüft automatisch den mittleren Commit
# Du testest: Bug vorhanden?
git bisect bad # Ja → bug-Seite markieren
git bisect good # Nein → good-Seite markieren
# Nach 7-10 Schritten:
# "abc1234 is the first bad commit"
git show abc1234 # Zeig Claude Code diesen Diff!
# Automatisiertes Bisect mit Testscript
git bisect run npx vitest run --reporter=verbose tests/auth.test.ts
Nach bisect: Diff analysieren lassen
"git bisect hat diesen Commit als ersten schlechten Commit identifiziert.
Hier ist der Diff: [git diff einfügen]. Identifiziere welche Änderung
in diesem Commit den Bug verursacht hat und erkläre warum."
Code-Abschnitte auskommentieren
Wenn du einen Bug isolieren möchtest, ohne git bisect — weil der Bug
vielleicht kein Regression ist — hilft systematisches Auskommentieren.
Claude Code gibt dir einen Plan, welche Abschnitte in welcher Reihenfolge.
// Schritt 1: Gesamte Pipeline auskommentieren
async function processOrder(orderId: string) {
const order = await fetchOrder(orderId); // ← Test: Bug hier?
// const validated = await validateOrder(order); ← auskommentiert
// const priced = await applyPricing(validated); ← auskommentiert
// await sendConfirmation(priced); ← auskommentiert
return order; // Nur Step 1 läuft — kein Bug? Weiter zu Step 2
}
// Schritt 2: Nächsten Abschnitt einkommentieren
async function processOrder(orderId: string) {
const order = await fetchOrder(orderId);
const validated = await validateOrder(order); // ← Bug tritt auf? Gefunden!
// const priced = await applyPricing(validated);
// await sendConfirmation(priced);
return validated;
}
Feature-Flags als Debug-Schalter
// Feature-Flag für Debug-Isolation
const DEBUG_FLAGS = {
useNewPricingEngine: process.env.DEBUG_NEW_PRICING === 'true',
skipCacheLayer: process.env.DEBUG_SKIP_CACHE === 'true',
verboseAuth: process.env.DEBUG_AUTH_VERBOSE === 'true',
} as const;
async function getPrice(productId: string) {
if (DEBUG_FLAGS.skipCacheLayer) {
return fetchPriceFromDb(productId); // Direkt DB — Cache-Bug ausschließen
}
return fetchPriceWithCache(productId);
}
# Deployment mit Debug-Flag
# DEBUG_SKIP_CACHE=true node app.js
# → Wenn Bug verschwindet: Cache ist die Ursache
Claude Code Prompt für Binary Search: "Ich habe eine komplexe
Pipeline mit 8 Schritten. Der Bug tritt irgendwo drin auf. Erstell mir einen
Binary-Search-Plan: welche Schritte soll ich in welcher Reihenfolge isolieren,
um den fehlerhaften Schritt in maximal 4 Tests zu finden?"
6 Logging-Strategie & Remote-Debugging
Gutes Logging ist Debugging auf Vorrat. Schlechtes Logging ist Information-Overload
ohne Signal. Claude Code hilft dir, eine Logging-Strategie zu entwerfen, die
genau dann die richtigen Daten liefert, wenn du sie brauchst.
Strukturiertes Logging
console.log("user:", user) ist für lokale Entwicklung akzeptabel.
In Production brauchst du strukturiertes JSON-Logging, das du filtern,
aggregieren und durchsuchen kannst.
// logger.ts — Strukturiertes Logging mit pino
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL ?? 'info',
formatters: {
level: (label) => ({ level: label }),
},
base: {
service: 'api',
version: process.env.npm_package_version,
env: process.env.NODE_ENV,
},
});
// Child-Logger für Request-Kontext
export function createRequestLogger(requestId: string, userId?: string) {
return logger.child({ requestId, userId });
}
// Verwendung
const log = createRequestLogger('req-abc123', 'user-42');
log.info({ orderId: 'ord-99', action: 'checkout' }, 'Checkout gestartet');
log.error({ err, orderId }, 'Checkout fehlgeschlagen');
// Output: {"level":"error","requestId":"req-abc123","userId":"user-42",
// "orderId":"ord-99","err":{"message":"..."},"msg":"Checkout fehlgeschlagen"}
Log-Level-Strategie
| Level | Wann | Beispiel |
TRACE |
Nur lokale Entwicklung, sehr granular |
Jeder DB-Query mit SQL |
DEBUG |
Diagnose-Informationen, Staging |
Auth-Flow-Schritte, Cache-Hits |
INFO |
Normaler Betrieb |
Request-Start, Business-Events |
WARN |
Unerwartet aber handhabbar |
Veraltete API genutzt, Retry |
ERROR |
Fehler der Aktion verhindert |
DB-Verbindung fehlgeschlagen |
FATAL |
System nicht mehr funktionsfähig |
Crash vor Neustart |
Debug-Middleware für Express/Hono/Fastify
// debug-middleware.ts
import { Request, Response, NextFunction } from 'express';
import { randomUUID } from 'crypto';
import { createRequestLogger } from './logger';
export function debugMiddleware(req: Request, res: Response, next: NextFunction) {
const requestId = randomUUID();
const startTime = Date.now();
const log = createRequestLogger(requestId, req.user?.id);
// Request-Kontext auf req hängen (für Handler verfügbar)
(req as any).log = log;
(req as any).requestId = requestId;
log.info({
method: req.method,
path: req.path,
query: req.query,
userAgent: req.headers['user-agent'],
}, 'Request started');
res.on('finish', () => {
const duration = Date.now() - startTime;
const level = res.statusCode >= 500 ? 'error' : res.statusCode >= 400 ? 'warn' : 'info';
log[level]({
statusCode: res.statusCode,
duration,
contentLength: res.getHeader('content-length'),
}, `Request completed in ${duration}ms`);
});
next();
}
Source Maps und Remote-Debugging
# tsconfig.json — Source Maps für Production-Debugging
{
"compilerOptions": {
"sourceMap": true,
"inlineSources": true, // Quellcode in Source-Map einbetten
"declarationMap": true
}
}
# Vite Build mit Source Maps
# vite.config.ts
# build: { sourcemap: 'hidden' } ← Kein Browser-Zugriff, aber Sentry kann es
# Remote-Debugging Node.js
node --inspect=0.0.0.0:9229 app.js # ← ACHTUNG: nur für lokales Netz!
node --inspect=127.0.0.1:9229 app.js # ← Sicher: nur localhost
# SSH-Tunnel für Remote-Server
ssh -L 9229:127.0.0.1:9229 user@server
# Dann in Chrome: chrome://inspect → localhost:9229
Prompt
"Reviewe meine Logging-Implementation. Welche kritischen Pfade haben
kein ausreichendes Logging? Wo werden Errors möglicherweise
verschluckt? Erstell mir eine Checklist der fehlenden Log-Statements."
- Alle Error-Handler loggen mit Stack-Trace
- Alle externen API-Calls haben Timeout-Logging
- Auth-Flows komplett nachvollziehbar
- Business-kritische Events (Purchase, Signup) auf INFO
- Keine Passwörter/Tokens in Logs
- Request-IDs durch die gesamte Pipeline
Security-Hinweis: Logge niemals Passwörter, Tokens, Session-Cookies
oder PII (Name, E-Mail, IBAN) in lesbarer Form. Claude Code weist dich darauf hin —
aber verlass dich nicht allein darauf. Nutze Log-Scrubbing-Libraries wie
pino-noir für automatische Filterung.
Claude Code jetzt kostenlos testen
Starte deinen kostenlosen Trial und erlebe, wie Claude Code deinen
Debugging-Workflow revolutioniert — von Stack-Trace-Analyse bis
zur automatischen Testgenerierung.
Jetzt Trial starten →
- 1Mindset: Kontext-reiche Prompts + Hypothesen-Loop statt "Warum crasht das?"
- 2Root-Cause: 5-Why-Methode + Typen als Fehlerdetektoren, niemals Symptoms maskieren
- 3Reproduktion: Minimales Reproduktionsbeispiel + failing Unit-Test schreiben lassen
- 4Stack-Trace: Vollständig einfügen, async-Bugs durch allSettled sichtbar machen
- 5Binary Search: git bisect automatisieren, Feature-Flags als Debug-Schalter nutzen
- 6Logging: Strukturiert + gelabelt + sicher — Claude Code reviewed deine Gaps