Claude
Skills
Sign in
Back

debugging-methodology

Included with Lifetime
$97 forever

Evidence-based debugging with Iron Law discipline. Instrument before guessing, trace before theorizing. Use when encountering any bug, test failure, or unexpected behavior - before proposing fixes.

Code Review

What this skill does


# Debugging Methodology

Stop guessing. Add instrumentation. Follow the evidence.

## The Iron Law

```
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
```

If you haven't gathered evidence, you cannot propose fixes.

**Rationale:** Random fixes waste time and create new bugs. Quick patches mask underlying issues. Systematic debugging is FASTER than guess-and-check thrashing.

## Core Principle

**Measure, don't speculate.** When something fails, the answer is never "maybe it's X" followed by random changes. The answer is: add instrumentation that produces the specific data needed to explain the failure.

## The Anti-Pattern

```
Problem occurs
  → "Maybe it's a race condition"
  → Change something random
  → Still broken
  → "Could be caching"
  → Change something else
  → User frustrated, hours wasted
```

**Why this happens:** Insufficient data. You're debugging blind.

## The Protocol

### 1. Document the Symptom Exactly

```typescript
// WRONG: "The API returns an error"
// RIGHT: Exact symptom with context

/*
Symptom: getUser returns err('DB_ERROR')
Expected: ok(user) with { id: '123', name: 'Alice' }
Actual: err('DB_ERROR')
Context: userId='123', environment=test, after migration
Error message: "Connection refused to localhost:5432"
*/
```

**Capture:**
- Exact error message (copy-paste, don't paraphrase)
- Expected vs actual behavior
- Input values that triggered the failure
- Environment context

### 2. Add Instrumentation FIRST

Before forming ANY hypothesis, make the invisible visible:

```typescript
// BEFORE: Silent failure
async function getUser(
  args: { userId: string },
  deps: GetUserDeps
): Promise<Result<User, 'NOT_FOUND' | 'DB_ERROR'>> {
  const user = await deps.db.findUser(args.userId);
  if (!user) return err('NOT_FOUND');
  return ok(user);
}

// AFTER: Observable failure
async function getUser(
  args: { userId: string },
  deps: GetUserDeps
): Promise<Result<User, 'NOT_FOUND' | 'DB_ERROR'>> {
  deps.logger.debug('getUser called', { userId: args.userId });

  try {
    deps.logger.debug('Calling db.findUser', { userId: args.userId });
    const user = await deps.db.findUser(args.userId);
    deps.logger.debug('db.findUser returned', {
      userId: args.userId,
      found: !!user,
      userKeys: user ? Object.keys(user) : null
    });

    if (!user) {
      deps.logger.info('User not found', { userId: args.userId });
      return err('NOT_FOUND');
    }

    deps.logger.debug('Returning user', { userId: user.id });
    return ok(user);
  } catch (error) {
    deps.logger.error('Database error in getUser', {
      userId: args.userId,
      error: error instanceof Error ? error.message : String(error),
      stack: error instanceof Error ? error.stack : undefined
    });
    return err('DB_ERROR');
  }
}
```

### 3. Instrument at Decision Points

Add logging at every branch, every external call, every state change:

| Point | What to Log |
|-------|-------------|
| Function entry | All args, relevant deps state |
| External calls | Request params, before and after |
| Conditionals | Which branch taken, why |
| Transformations | Input shape, output shape |
| Error paths | Full error object, context |
| Function exit | Return value, duration |

### 4. Use OpenTelemetry Spans

For complex flows, use tracing:

```typescript
import { trace } from 'autotel';

async function processOrder(
  args: { orderId: string },
  deps: ProcessOrderDeps
): Promise<Result<Order, OrderError>> {
  return trace('processOrder', { orderId: args.orderId }, async (span) => {
    span.setAttribute('order.id', args.orderId);

    const validationResult = await trace('validateOrder', async () => {
      return validateOrder(args, deps);
    });

    if (!validationResult.ok) {
      span.setAttribute('error.type', validationResult.error);
      return validationResult;
    }

    const paymentResult = await trace('processPayment', async () => {
      return processPayment({ order: validationResult.value }, deps);
    });

    span.setAttribute('payment.success', paymentResult.ok);
    return paymentResult;
  });
}
```

### 5. Form Evidence-Based Hypothesis

**Only AFTER you have data:**

```
Evidence from logs:
- getUser called with userId='123' ✓
- db.findUser called ✓
- db.findUser threw: "Connection refused to localhost:5432"

Hypothesis: Database connection failed
Evidence: Error message explicitly says "Connection refused"
Test: Check if database is running, check connection string
```

**Hypothesis must:**
- Be based on observed data (not speculation)
- Explain ALL symptoms
- Be testable with a single change

### 6. One Change at a Time

```
WRONG:
  → Change connection string AND add retry AND increase timeout
  → "It works now!" (but which fix was it?)

RIGHT:
  → Check database is running (it wasn't)
  → Start database
  → Retest → Works
  → Root cause: Database container wasn't started
```

## Result Type Debugging

When debugging Result-returning functions:

### Check the Error Path

```typescript
// Add explicit logging for err() returns
if (!user) {
  const errorContext = {
    userId: args.userId,
    dbQueryExecuted: true,
    queryDuration: Date.now() - startTime,
  };
  deps.logger.info('Returning NOT_FOUND', errorContext);
  return err('NOT_FOUND');
}
```

### Trace Through Workflows

```typescript
// When createWorkflow fails, add step-level logging
const workflow = createWorkflow()
  .pipe(step('validate', async (input) => {
    deps.logger.debug('Step: validate', { input });
    const result = await validate(input, deps);
    deps.logger.debug('Step: validate result', { ok: result.ok });
    return result;
  }))
  .pipe(step('process', async (input) => {
    deps.logger.debug('Step: process', { input });
    const result = await process(input, deps);
    deps.logger.debug('Step: process result', { ok: result.ok });
    return result;
  }));
```

### Verify Deps Are Correct

```typescript
// When mocking in tests, verify deps behavior
it('returns NOT_FOUND when user missing', async () => {
  const deps = mock<GetUserDeps>();
  deps.db.findUser.mockResolvedValue(null);

  // Debug: verify mock is configured correctly
  console.log('Mock configured:', {
    findUser: deps.db.findUser.getMockName(),
    mockReturnValue: await deps.db.findUser('test'),
  });

  const result = await getUser({ userId: '123' }, deps);

  // Debug: verify what was called
  console.log('findUser called with:', deps.db.findUser.mock.calls);
  console.log('Result:', result);

  expect(result.ok).toBe(false);
});
```

## Debugging Checklist

Before guessing, answer these:

| Question | How to Answer |
|----------|---------------|
| What are the input values? | Log function entry |
| Which code path executed? | Log at conditionals |
| What did external calls return? | Log before/after deps calls |
| What error was thrown? | Log in catch block with stack |
| What was the return value? | Log function exit |

## Anti-Patterns

### Never Guess

```
WRONG: "It's probably a race condition"
RIGHT: "Let me add timestamps to see the execution order"

WRONG: "Maybe the mock isn't working"
RIGHT: "Let me log what the mock returns when called"

WRONG: "Could be a caching issue"
RIGHT: "Let me log cache hits/misses with keys"
```

### Never Change Multiple Things

```
WRONG: Fix A, B, and C → "It works!" (which one fixed it?)
RIGHT: Fix A → Test → Still broken → Fix B → Test → Works (B was the issue)
```

### Never Remove Instrumentation Prematurely

```
WRONG: Problem seems fixed → Delete all logging
RIGHT: Problem confirmed fixed → Keep structured logging, remove debug-level only
```

### Never Assume Code Does What It Says

```typescript
// Function is CALLED getUser but does it RETURN user?
async function getUser(args, deps) {
  // Don't assume. Log the actual return value.
  const result = /* ... */;
  deps.logger.debug('getUser returning', { result });
  return result;
}
```

## Integration with Testing

### Debug Failing Tests Systematically

```typescript
it('pr

Related in Code Review