Claude
Skills
Sign in
Back

debugging-methodology

Included with Lifetime
$97 forever

Scientific debugging methodology including hypothesis-driven debugging, bug reproduction, binary search debugging, stack trace analysis, logging strategies, and root cause analysis. Use when debugging errors, analyzing stack traces, investigating bugs, or troubleshooting performance issues.

General

What this skill does


# Debugging Methodology

This skill provides comprehensive guidance for systematically debugging issues using scientific methods and proven techniques.

## Scientific Debugging Method

### The Scientific Approach

**1. Observe**: Gather information about the bug
**2. Hypothesize**: Form theories about the cause
**3. Test**: Design experiments to test hypotheses
**4. Analyze**: Evaluate results
**5. Conclude**: Fix the bug or refine hypothesis

### Example: Debugging a Login Issue

```typescript
// Bug: Users cannot log in

// 1. OBSERVE
// - Error message: "Invalid credentials"
// - Happens for all users
// - Started after last deployment
// - Logs show: "bcrypt compare failed"

// 2. HYPOTHESIZE
// Hypothesis 1: Password comparison logic is broken
// Hypothesis 2: Database passwords corrupted
// Hypothesis 3: Bcrypt library updated with breaking change

// 3. TEST
// Test 1: Check if bcrypt library version changed
const packageLock = await fs.readFile('package-lock.json');
// Result: bcrypt upgraded from 5.0.0 to 6.0.0

// Test 2: Check bcrypt changelog
// Result: v6.0.0 changed default salt rounds

// Test 3: Verify password hashing
const testPassword = 'password123';
const oldHash = '$2b$10$...'; // From database
const newHash = await bcrypt.hash(testPassword, 10);
console.log(await bcrypt.compare(testPassword, oldHash)); // false
console.log(await bcrypt.compare(testPassword, newHash)); // true

// 4. ANALYZE
// Old hashes use $2b$ format, new version uses $2a$ format
// Incompatible hash formats

// 5. CONCLUDE
// Rollback bcrypt to 5.x or migrate all password hashes
```

## Reproducing Bugs Consistently

### Creating Minimal Reproduction

```typescript
// Original bug report: "App crashes when clicking submit"

// Step 1: Remove unrelated code
// ❌ BAD - Too much noise
function handleSubmit() {
  validateForm();
  checkPermissions();
  logAnalytics();
  sendToServer();
  updateUI();
  showNotification();
  // Which one causes the crash?
}

// ✅ GOOD - Minimal reproduction
function handleSubmit() {
  // Removed: validateForm, checkPermissions, logAnalytics, updateUI, showNotification
  // Bug still occurs with just:
  sendToServer();
  // Root cause: sendToServer crashes with undefined data
}
```

### Reproducing Race Conditions

```typescript
// Bug: Intermittent "Cannot read property of undefined"

// Make race condition reproducible with delays
async function fetchUserData() {
  const user = await fetchUser();
  // Add artificial delay to make race condition consistent
  await new Promise(resolve => setTimeout(resolve, 100));
  return user.profile; // Sometimes undefined
}

// Once reproducible, investigate:
// - Are multiple requests racing?
// - Is data being cleared too early?
// - Are promises resolving out of order?
```

### Creating Test Cases

```typescript
// Once bug is reproducible, create failing test
describe('Login', () => {
  test('should authenticate user with valid credentials', async () => {
    const user = await db.user.create({
      email: '[email protected]',
      password: await bcrypt.hash('password123', 10),
    });

    const result = await login('[email protected]', 'password123');

    expect(result.success).toBe(true);
    expect(result.user.email).toBe('[email protected]');
  });
});
```

## Binary Search Debugging

### Finding the Breaking Commit

```bash
# Use git bisect to find the commit that introduced the bug

# Start bisect
git bisect start

# Mark current commit as bad (has the bug)
git bisect bad

# Mark a known good commit (before bug appeared)
git bisect good v1.2.0

# Git will checkout a commit in the middle
# Test if bug exists, then mark:
git bisect bad   # Bug exists in this commit
# or
git bisect good  # Bug doesn't exist in this commit

# Repeat until git identifies the breaking commit
# Git will output: "abc123 is the first bad commit"

# End bisect session
git bisect reset
```

### Automated Bisect

```bash
# Create test script that exits 0 (pass) or 1 (fail)
# test.sh
#!/bin/bash
npm test 2>&1 | grep -q "Login test failed"
if [ $? -eq 0 ]; then
  exit 1  # Bug found
else
  exit 0  # Bug not found
fi

# Run automated bisect
git bisect start HEAD v1.2.0
git bisect run ./test.sh

# Git will automatically find the breaking commit
```

### Binary Search in Code

```typescript
// Bug: Function returns wrong result for large arrays

function processArray(arr: number[]): number {
  // 100 lines of code
  // Which line causes the bug?
}

// Binary search approach:
// 1. Comment out second half
function processArray(arr: number[]): number {
  // Lines 1-50
  // Lines 51-100 (commented out)
}
// If bug disappears: Bug is in lines 51-100
// If bug persists: Bug is in lines 1-50

// 2. Repeat on the problematic half
// Continue until you isolate the buggy line
```

## Stack Trace Analysis

### Reading Stack Traces

```
Error: Cannot read property 'name' of undefined
    at getUserName (/app/src/user.ts:42:20)
    at formatUserProfile (/app/src/profile.ts:15:25)
    at handleRequest (/app/src/api.ts:89:30)
    at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)
```

**Analysis**:
1. **Error type**: TypeError - trying to access property on undefined
2. **Error message**: "Cannot read property 'name' of undefined"
3. **Origin**: `getUserName` function at line 42
4. **Call chain**: api.ts → profile.ts → user.ts
5. **Root cause location**: user.ts:42

### Investigating the Stack Trace

```typescript
// user.ts:42
function getUserName(userId: string): string {
  const user = cache.get(userId);
  return user.name; // ← Line 42: user is undefined
}

// Why is user undefined?
// 1. Check cache.get implementation
// 2. Check if userId is valid
// 3. Check if user exists in cache

// Add defensive check:
function getUserName(userId: string): string {
  const user = cache.get(userId);
  if (!user) {
    throw new Error(`User not found in cache: ${userId}`);
  }
  return user.name;
}
```

### Source Maps for Production

```javascript
// Enable source maps in production
// webpack.config.js
module.exports = {
  devtool: 'source-map',
  // This generates .map files for production debugging
};

// View original TypeScript code in production errors
// Instead of:
//   at r (/app/bundle.js:1:23456)
// You see:
//   at getUserName (/app/src/user.ts:42:20)
```

## Logging Strategies

### Strategic Log Placement

```typescript
// ✅ GOOD - Log at key decision points
async function processOrder(order: Order) {
  logger.info('Processing order', { orderId: order.id, items: order.items.length });

  try {
    // Log before critical operations
    logger.debug('Validating order', { orderId: order.id });
    await validateOrder(order);

    logger.debug('Processing payment', { orderId: order.id, amount: order.total });
    const payment = await processPayment(order);

    logger.info('Order processed successfully', {
      orderId: order.id,
      paymentId: payment.id,
      duration: Date.now() - startTime,
    });

    return payment;
  } catch (error) {
    // Log errors with context
    logger.error('Order processing failed', {
      orderId: order.id,
      error: error.message,
      stack: error.stack,
    });
    throw error;
  }
}
```

### Structured Logging

```typescript
import winston from 'winston';

const logger = winston.createLogger({
  level: process.env.LOG_LEVEL || 'info',
  format: winston.format.combine(
    winston.format.timestamp(),
    winston.format.errors({ stack: true }),
    winston.format.json()
  ),
  defaultMeta: {
    service: 'order-service',
    version: process.env.APP_VERSION,
  },
  transports: [
    new winston.transports.File({ filename: 'error.log', level: 'error' }),
    new winston.transports.File({ filename: 'combined.log' }),
  ],
});

// Output:
// {
//   "timestamp": "2025-10-16T10:30:00.000Z",
//   "level": "error",
//   "message": "Order processing failed",
//   "orderId": "order_123",
//   "error": "Payment declined",
//   "service": "order-servi

Related in General