debugging-methodology
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.
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('prRelated in Code Review
gstack
IncludedFast headless browser for QA testing and site dogfooding. Navigate pages, interact with elements, verify state, diff before/after, take annotated screenshots, test responsive layouts, forms, uploads, dialogs, and capture bug evidence. Use when asked to open or test a site, verify a deployment, dogfood a user flow, or file a bug with screenshots. (gstack)
startup-due-diligence
IncludedLegal due diligence review for seed-stage and Series A startups (US, Delaware C-Corp focus). Supports both investor and founder perspectives. Capabilities include: (1) Interactive document review and issue spotting; (2) Document request list generation; (3) Cap table and SAFE/convertible note analysis; (4) Red flag identification with severity ratings; (5) Diligence report generation. TRIGGERS: due diligence, DD, startup investment, cap table review, Series A, seed round, investor diligence, legal review startup, SAFE analysis, convertible note, 409A, founder vesting.
interview-master
IncludedThis skill should be used when the user asks to "generate interview questions", "prepare for interview", "optimize resume", "conduct mock interview", "analyze git commits for resume", "generate resume from code", "review my resume", or mentions interview preparation, career assistance, or extracting project experience from git history. Provides comprehensive interview and career development guidance for both job seekers and interviewers.
fix-issue
IncludedFixes GitHub issues using parallel analysis agents for root cause investigation, code exploration, and regression detection. Reads issue context from gh CLI, searches codebase and memory for related patterns, generates a fix with tests, and links the resolution back to the issue via PR. Includes prevention analysis to avoid recurrence. Use when debugging errors, resolving regressions, fixing bugs, or triaging issues.
sf-apex
IncludedGenerates and reviews Salesforce Apex code with 150-point scoring. TRIGGER when: user writes, reviews, or fixes Apex classes, triggers, test classes, batch/queueable/schedulable jobs, or touches .cls/.trigger files. DO NOT TRIGGER when: LWC JavaScript (use sf-lwc), Flow XML (use sf-flow), SOQL-only queries (use sf-soql), or non-Salesforce code.
swift-development
IncludedComprehensive Swift development for building, testing, and deploying iOS/macOS applications. Use when Claude needs to: (1) Build Swift packages or Xcode projects from command line, (2) Run tests with XCTest or Swift Testing framework, (3) Manage iOS simulators with simctl, (4) Handle code signing, provisioning profiles, and app distribution, (5) Format or lint Swift code with SwiftFormat/SwiftLint, (6) Work with Swift Package Manager (SPM), (7) Implement Swift 6 concurrency patterns (async/await, actors, Sendable), (8) Create SwiftUI views with MVVM architecture, (9) Set up Core Data or SwiftData persistence, or any other Swift/iOS/macOS development tasks.