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.
Applies scientific debugging methods like hypothesis-driven debugging, binary search, and root cause analysis when you encounter errors, analyze stack traces, or troubleshoot performance issues.
/plugin marketplace add webdevtodayjason/titanium-plugins/plugin install titanium-toolkit@titanium-pluginsThis skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill provides comprehensive guidance for systematically debugging issues using scientific methods and proven techniques.
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
// 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
// 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
}
// 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?
// Once bug is reproducible, create failing test
describe('Login', () => {
test('should authenticate user with valid credentials', async () => {
const user = await db.user.create({
email: 'test@example.com',
password: await bcrypt.hash('password123', 10),
});
const result = await login('test@example.com', 'password123');
expect(result.success).toBe(true);
expect(result.user.email).toBe('test@example.com');
});
});
# 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
# 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
// 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
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:
getUserName function at line 42// 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;
}
// 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)
// ✅ 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;
}
}
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-service",
// "version": "1.2.3"
// }
// Use appropriate log levels
logger.debug('Detailed debug information'); // Development only
logger.info('Normal operation'); // Informational
logger.warn('Warning but not an error'); // Potential issues
logger.error('Error occurred'); // Errors that need attention
logger.fatal('Critical failure'); // System-wide failures
// Set log level by environment
const logLevel = {
development: 'debug',
staging: 'info',
production: 'warn',
}[process.env.NODE_ENV];
// Set breakpoints in VS Code
function calculateTotal(items: Item[]): number {
let total = 0;
for (const item of items) {
debugger; // Execution pauses here
total += item.price * item.quantity;
}
return total;
}
// Or use VS Code launch.json
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Tests",
"program": "${workspaceFolder}/node_modules/.bin/jest",
"args": ["--runInBand"],
"console": "integratedTerminal"
}
]
}
# Start Node with inspector
node --inspect index.js
# Open Chrome DevTools
# Navigate to: chrome://inspect
# Click "inspect" on your Node.js process
# Or use Node's built-in debugger
node inspect index.js
> cont # Continue
> next # Step over
> step # Step into
> out # Step out
> repl # Enter REPL to inspect variables
// Detect memory leaks
import v8 from 'v8';
import fs from 'fs';
// Take heap snapshot
function takeHeapSnapshot(filename: string) {
const snapshot = v8.writeHeapSnapshot(filename);
console.log(`Heap snapshot written to ${snapshot}`);
}
// Compare snapshots to find leaks
takeHeapSnapshot('before.heapsnapshot');
// ... run code that might leak
takeHeapSnapshot('after.heapsnapshot');
// Analyze in Chrome DevTools:
// 1. Open DevTools → Memory tab
// 2. Load snapshot
// 3. Compare snapshots
// 4. Look for objects that grew significantly
// Profile function execution time
console.time('processLargeArray');
processLargeArray(data);
console.timeEnd('processLargeArray');
// Output: processLargeArray: 1234.567ms
// More detailed profiling
import { performance } from 'perf_hooks';
const start = performance.now();
processLargeArray(data);
const end = performance.now();
console.log(`Execution time: ${end - start}ms`);
# Generate CPU profile
node --prof index.js
# Process profile data
node --prof-process isolate-0x*.log > profile.txt
# Analyze profile.txt to find slow functions
// Add performance marks
performance.mark('start-data-processing');
processData(data);
performance.mark('end-data-processing');
performance.measure(
'data-processing',
'start-data-processing',
'end-data-processing'
);
const measure = performance.getEntriesByName('data-processing')[0];
console.log(`Data processing took ${measure.duration}ms`);
import axios from 'axios';
// Add request/response interceptors
axios.interceptors.request.use(
(config) => {
console.log('Request:', {
method: config.method,
url: config.url,
headers: config.headers,
data: config.data,
});
return config;
},
(error) => {
console.error('Request error:', error);
return Promise.reject(error);
}
);
axios.interceptors.response.use(
(response) => {
console.log('Response:', {
status: response.status,
headers: response.headers,
data: response.data,
});
return response;
},
(error) => {
console.error('Response error:', {
status: error.response?.status,
data: error.response?.data,
message: error.message,
});
return Promise.reject(error);
}
);
// Enable detailed CORS logging
import cors from 'cors';
const corsOptions = {
origin: (origin, callback) => {
console.log('CORS request from origin:', origin);
const allowedOrigins = ['https://app.example.com'];
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
console.log('CORS blocked:', origin);
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
};
app.use(cors(corsOptions));
// ❌ BAD - Race condition
let userData = null;
async function loadUser() {
userData = await fetchUser(); // Takes 100ms
}
async function displayUser() {
console.log(userData.name); // May be null if loadUser not finished
}
loadUser();
displayUser(); // Race condition!
// ✅ GOOD - Wait for promise
async function main() {
await loadUser();
await displayUser(); // userData guaranteed to be loaded
}
// Detect race conditions with version numbers
interface Document {
id: string;
content: string;
version: number;
}
async function updateDocument(doc: Document) {
// Read current version
const current = await db.document.findUnique({
where: { id: doc.id },
});
// Check if version matches
if (current.version !== doc.version) {
throw new Error('Document was modified by another user');
}
// Update with incremented version
await db.document.update({
where: { id: doc.id, version: doc.version },
data: {
content: doc.content,
version: doc.version + 1,
},
});
}
// ❌ BAD - Off by one
const arr = [1, 2, 3, 4, 5];
for (let i = 0; i <= arr.length; i++) {
console.log(arr[i]); // Last iteration: undefined
}
// ✅ GOOD
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
// ❌ BAD - No null check
function getUserName(user: User): string {
return user.profile.name; // Crashes if user or profile is null
}
// ✅ GOOD - Defensive checks
function getUserName(user: User | null): string {
if (!user) {
return 'Unknown';
}
if (!user.profile) {
return 'No profile';
}
return user.profile.name;
}
// ✅ BETTER - Optional chaining
function getUserName(user: User | null): string {
return user?.profile?.name ?? 'Unknown';
}
// ❌ BAD - Forgot await
async function getUser(id: string) {
const user = fetchUser(id); // Missing await!
return user.name; // user is a Promise, not the actual user
}
// ✅ GOOD
async function getUser(id: string) {
const user = await fetchUser(id);
return user.name;
}
// ❌ BAD - Sequential when parallel is possible
async function loadData() {
const users = await fetchUsers(); // 1 second
const posts = await fetchPosts(); // 1 second
const comments = await fetchComments(); // 1 second
// Total: 3 seconds
}
// ✅ GOOD - Parallel execution
async function loadData() {
const [users, posts, comments] = await Promise.all([
fetchUsers(),
fetchPosts(),
fetchComments(),
]);
// Total: 1 second
}
Problem: Application crashed in production
Why? Memory leak caused out-of-memory error
Why? Array of user sessions kept growing
Why? Sessions weren't being cleaned up
Why? Cleanup function wasn't being called
Why? Event listener for cleanup was never registered
Root Cause: Missing initialization code in new deployment script
## Root Cause Analysis
**Date**: 2025-10-16
**Incident**: API downtime (30 minutes)
### Timeline
- 10:00 - Deployment started
- 10:15 - First error reports
- 10:20 - Incident declared
- 10:25 - Rollback initiated
- 10:30 - Service restored
### Impact
- 500 users affected
- 10% of API requests failed
- $5,000 estimated revenue loss
### Root Cause
Database connection pool exhausted due to missing connection cleanup in new feature code.
### 5 Whys
1. Why did the API fail? → Database connections exhausted
2. Why were connections exhausted? → Connections not returned to pool
3. Why weren't connections returned? → Missing finally block in new code
4. Why was the finally block missing? → Code review missed it
5. Why did code review miss it? → No automated check for connection cleanup
### Immediate Actions Taken
- Rolled back deployment
- Manually closed leaked connections
- Service restored
### Preventive Measures
1. Add linter rule to detect missing finally blocks
2. Add integration test for connection cleanup
3. Update code review checklist
4. Add monitoring for connection pool usage
### Lessons Learned
- Need better monitoring of connection pool metrics
- Database connection patterns should be abstracted
- Code review process needs improvement
Before Debugging:
During Debugging:
After Fixing:
Use this skill when:
Remember: Debugging is detective work. Be systematic, stay curious, and always document what you learn. The bug you fix today will teach you how to prevent similar bugs tomorrow.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.