systematic-debugger
npx machina-cli add skill JasonWarrenUK/claude-code-config/systematic-debugger --openclawSystematic Debugging
Methodical approach to identifying and fixing software issues. Emphasizes reproducibility, isolation, and verification over trial-and-error debugging. Covers browser DevTools, Node debugging, logging strategies, and Svelte-specific debugging techniques.
When This Skill Applies
Use this skill when:
- User reports something "not working"
- Investigating errors or unexpected behaviour
- Performance issues need diagnosis
- Setting up debugging infrastructure
- Explaining debugging approaches
- Teaching debugging techniques
- Questions about DevTools or debugging tools
The Debugging Methodology
Four-step process: Reproduce → Isolate → Fix → Verify
1. Reproduce
Make the bug happen reliably
Can't fix what you can't reproduce. First priority is finding reliable steps to trigger the issue.
Questions to ask:
- What exact steps produce the error?
- Does it happen every time or intermittently?
- What's the expected vs actual behaviour?
- What's the minimal reproduction case?
Document reproduction steps:
## To Reproduce
1. Navigate to `/dashboard`
2. Click "Load More" button
3. Scroll to bottom of page
4. Click "Load More" again
**Expected**: More items load
**Actual**: Page freezes, console shows error
**Frequency**: Happens every time on 2nd click
If intermittent:
- Look for race conditions
- Check network timing issues
- Consider state-dependent bugs
- Try multiple environments
2. Isolate (Root-Cause Depth)
Narrow down the cause — then go deeper.
Once reproducible, determine exactly where the problem originates. But don't stop at the first explanation. The first "cause" is often a symptom. Ask why repeatedly until you reach the structural root.
The Five Whys:
Bug: Users see stale data after updating their profile.
Why? → The cache isn't invalidated after the update.
Why? → The update function doesn't call cache.invalidate().
Why? → The caching layer was added after the update function was written.
Why? → There's no pattern ensuring new writes invalidate related caches.
Root: Missing cache invalidation convention. Fix the convention, not just this instance.
Depth vs speed: Not every bug warrants five whys. Use your judgment:
- Shallow fix appropriate: Typo, wrong variable name, missing null check
- Deep investigation warranted: Bug that could recur, affects multiple users, or reveals a pattern gap
Isolation techniques:
Binary search approach:
// Working at line 50?
console.log('Check 1:', data); // ✓ Data good here
// Working at line 75?
console.log('Check 2:', result); // ✗ Result undefined here
// Problem is between lines 50-75
Comment out code:
// Does removing this fix it?
// await someAsyncFunction();
// If yes, problem is in someAsyncFunction
Minimal reproduction:
// Strip away everything non-essential
// Original: 300 lines, complex state, multiple API calls
// Minimal: 20 lines that show the exact issue
async function minimalRepro() {
const data = await fetch('/api/items');
console.log(data); // undefined when expected array
}
Check assumptions:
// Assumption: API returns array
console.log(typeof data); // "object" - it's null!
// Assumption: User is logged in
console.log(user); // undefined - not logged in!
3. Fix
Implement targeted solution
Now that you know the cause, fix it specifically.
Fix patterns:
Null/undefined checks:
// Before (crashes)
const count = items.length;
// After
const count = items?.length ?? 0;
Async timing:
// Before (race condition)
fetch('/api/data');
renderUI(); // Renders before data arrives
// After
const data = await fetch('/api/data');
renderUI(data);
State initialization:
// Before (undefined on first render)
let items;
// After
let items = [];
Error boundaries:
// Before (crashes entire app)
const result = riskyOperation();
// After
try {
const result = riskyOperation();
} catch (error) {
console.error('Operation failed:', error);
showErrorToUser('Something went wrong');
}
4. Verify
Confirm the fix works
Don't assume it's fixed. Test thoroughly.
Verification checklist:
- ✓ Original reproduction steps no longer produce error
- ✓ Expected behaviour now occurs
- ✓ No new errors introduced
- ✓ Edge cases still work
- ✓ Performance not degraded
Test edge cases:
// Fixed for normal case, but what about:
- Empty array
- Null values
- Very large datasets
- Network failures
- Simultaneous requests
Write regression test (see Testing Foundations skill):
it('should handle second "Load More" click', async () => {
render(ItemList);
await clickLoadMore();
await clickLoadMore(); // This used to crash
expect(screen.getAllByRole('listitem').length).toBeGreaterThan(10);
});
Browser DevTools
Console
Strategic logging:
// ✗ Bad: Non-informative
console.log(data);
// ✓ Good: Labeled and contextual
console.log('API Response:', data);
console.log('User state before update:', user);
// ✓ Better: Grouped related logs
console.group('Data Processing');
console.log('Input:', rawData);
console.log('Processed:', processedData);
console.log('Output:', finalResult);
console.groupEnd();
// ✓ Advanced: Conditional logging
const DEBUG = true;
DEBUG && console.log('Debug info:', state);
// ✓ Tables for arrays of objects
console.table(users);
Console methods:
console.log('Normal message');
console.info('Informational');
console.warn('Warning - check this');
console.error('Error occurred');
console.debug('Debug details');
// Timing
console.time('operation');
// ... code to measure
console.timeEnd('operation'); // operation: 45.2ms
// Assertions
console.assert(value > 0, 'Value must be positive:', value);
// Count occurrences
console.count('API call'); // API call: 1
console.count('API call'); // API call: 2
Network Tab
Inspect API requests:
- Check request URL and method
- Verify headers (Authorization, Content-Type)
- Inspect request payload
- Check response status and body
- Look for failed requests (red)
- Check timing (slow responses)
Common issues:
404 Not Found → Check URL spelling, route exists
401 Unauthorized → Check auth token present and valid
500 Server Error → Check server logs, API issue
CORS Error → Check server allows origin
Timeout → API too slow or not responding
Elements Tab
Inspect DOM:
- Check element exists
- Verify classes applied
- Check computed styles
- Inspect event listeners
- Modify styles live
Common CSS issues:
/* Check computed styles for: */
- Display: none (hidden element)
- Z-index conflicts
- Overflow: hidden (content clipped)
- Position issues
- Flexbox/Grid properties
Sources Tab (Debugger)
Breakpoints:
function processData(data) {
// Set breakpoint on this line
const result = transform(data);
// Execution pauses, inspect:
// - data value
// - Scope variables
// - Call stack
return result;
}
Breakpoint types:
- Line breakpoints - Pause at specific line
- Conditional breakpoints - Pause only when condition true
- Logpoints - Log without stopping execution
- Exception breakpoints - Pause on errors
Debugger controls:
- Continue (F8) - Resume execution
- Step over (F10) - Execute current line, don't go into functions
- Step into (F11) - Go into function calls
- Step out (Shift+F11) - Exit current function
Application Tab
Inspect storage:
- Local Storage - Persistent key-value storage
- Session Storage - Session-only storage
- Cookies - Check authentication cookies
- IndexedDB - Client-side database
- Cache Storage - Service worker cache
Common storage issues:
// Check if data stored correctly
localStorage.getItem('user'); // "undefined" as string? null?
// Check cookie domain/path
// Check expiration
// Check HttpOnly/Secure flags
Performance Tab
Profile performance:
- Click record
- Perform slow action
- Stop recording
- Analyze flame chart
Look for:
- Long tasks (>50ms)
- Excessive re-renders
- Slow API calls
- Memory leaks (increasing usage)
Node.js Debugging
Built-in Debugger
Start with inspect:
node --inspect server.js
# or
node --inspect-brk server.js # Break on first line
Chrome DevTools:
- Open chrome://inspect
- Click "Open dedicated DevTools for Node"
- Same interface as browser debugging
Console Debugging
// Strategic placement
async function fetchUserData(userId) {
console.log('Fetching user:', userId);
const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
console.log('Query result:', user);
if (!user) {
console.log('User not found');
return null;
}
console.log('Returning user:', user);
return user;
}
Debug Module
import debug from 'debug';
const log = debug('app:users');
log('Fetching user %s', userId); // Only shows if DEBUG=app:* enabled
// Enable specific namespaces
// DEBUG=app:users node server.js
// DEBUG=app:* node server.js
// DEBUG=* node server.js
Svelte-Specific Debugging
Reactive Statements
Log reactive changes:
<script>
let count = 0;
// Debug reactive statement
$: console.log('Count changed:', count);
// Debug derived value
$: doubled = count * 2;
$: console.log('Doubled:', doubled);
// Debug with condition
$: if (count > 10) {
console.log('Count exceeded threshold');
}
</script>
Component Lifecycle
<script>
import { onMount, onDestroy, beforeUpdate, afterUpdate } from 'svelte';
onMount(() => {
console.log('Component mounted');
return () => console.log('Component cleanup');
});
onDestroy(() => {
console.log('Component destroyed');
});
beforeUpdate(() => {
console.log('Before DOM update');
});
afterUpdate(() => {
console.log('After DOM update');
});
</script>
Store Debugging
import { writable } from 'svelte/store';
function createDebugStore(name, initial) {
const store = writable(initial);
// Log all updates
const { subscribe, set, update } = store;
return {
subscribe,
set: (value) => {
console.log(`${name} set to:`, value);
set(value);
},
update: (fn) => {
console.log(`${name} updated`);
update((val) => {
const newVal = fn(val);
console.log(` Old:`, val, `New:`, newVal);
return newVal;
});
}
};
}
export const userStore = createDebugStore('user', null);
Svelte DevTools
Browser extension - Install Svelte DevTools
Features:
- Component tree inspection
- Props and state values
- Store contents
- Event listeners
- Performance profiling
Logging Strategies
Log Levels
const LOG_LEVELS = {
ERROR: 0,
WARN: 1,
INFO: 2,
DEBUG: 3
};
const currentLevel = LOG_LEVELS.INFO;
function log(level, message, ...args) {
if (level <= currentLevel) {
const prefix = ['ERROR', 'WARN', 'INFO', 'DEBUG'][level];
console.log(`[${prefix}]`, message, ...args);
}
}
// Usage
log(LOG_LEVELS.ERROR, 'Failed to load user');
log(LOG_LEVELS.DEBUG, 'Cache hit for key:', key); // Only shows if DEBUG level
Contextual Logging
function createLogger(context) {
return {
info: (msg, ...args) => console.log(`[${context}]`, msg, ...args),
error: (msg, ...args) => console.error(`[${context}]`, msg, ...args),
debug: (msg, ...args) => console.debug(`[${context}]`, msg, ...args)
};
}
const log = createLogger('UserService');
log.info('Fetching user', userId);
log.error('Failed to fetch', error);
Production Logging
Don't ship debug logs:
// ✗ Bad: Logs in production
console.log('User clicked button');
// ✓ Good: Conditional logging
if (import.meta.env.DEV) {
console.log('User clicked button');
}
// ✓ Better: Logging service
logger.info('User action', { action: 'button_click', userId });
Production-safe logging:
// Errors only in production
if (import.meta.env.PROD) {
window.addEventListener('error', (event) => {
logToService({
message: event.message,
stack: event.error?.stack,
timestamp: new Date().toISOString()
});
});
}
Common Bug Patterns
Race Conditions
Problem:
// Race: Which finishes first?
fetchUserData(userId);
fetchUserPosts(userId);
render(); // Might render before data arrives
Solution:
const [userData, userPosts] = await Promise.all([
fetchUserData(userId),
fetchUserPosts(userId)
]);
render(userData, userPosts);
Stale Closures
Problem:
<script>
let count = 0;
function startTimer() {
setInterval(() => {
console.log(count); // Always logs 0 (stale closure)
count++;
}, 1000);
}
</script>
Solution:
<script>
let count = 0;
function startTimer() {
setInterval(() => {
count = count + 1; // Access current value via update
}, 1000);
}
// Or use reactive statement
$: if (timerActive) {
const interval = setInterval(() => count++, 1000);
return () => clearInterval(interval);
}
</script>
Undefined Reference Errors
Problem:
const name = user.profile.name; // Cannot read property 'name' of undefined
Solution:
// Optional chaining
const name = user?.profile?.name;
// With default
const name = user?.profile?.name ?? 'Unknown';
// Defensive check
if (user?.profile?.name) {
const name = user.profile.name;
}
Memory Leaks
Problem:
<script>
import { onMount } from 'svelte';
onMount(() => {
const interval = setInterval(() => {
// Do work
}, 1000);
// Never cleaned up! Memory leak
});
</script>
Solution:
<script>
import { onMount } from 'svelte';
onMount(() => {
const interval = setInterval(() => {
// Do work
}, 1000);
return () => clearInterval(interval); // Cleanup
});
</script>
Async State Updates
Problem:
async function loadData() {
loading = true;
const data = await fetchData();
items = data; // If component unmounted, this updates destroyed component
loading = false;
}
Solution:
async function loadData() {
loading = true;
let cancelled = false;
onDestroy(() => {
cancelled = true;
});
const data = await fetchData();
if (!cancelled) {
items = data;
loading = false;
}
}
Performance Debugging
Identify Slow Operations
function measurePerformance(label, fn) {
const start = performance.now();
const result = fn();
const end = performance.now();
console.log(`${label}: ${(end - start).toFixed(2)}ms`);
return result;
}
const result = measurePerformance('Data processing', () => {
return processLargeDataset(data);
});
Profile Re-renders
<script>
import { afterUpdate } from 'svelte';
let updateCount = 0;
afterUpdate(() => {
updateCount++;
console.log('Component re-rendered', updateCount, 'times');
});
</script>
Find Memory Leaks
Chrome DevTools Memory Tab:
- Take heap snapshot
- Perform action
- Take another snapshot
- Compare snapshots
- Look for growing objects
TypeScript Debugging
Type Errors as Debugging Aid
// TypeScript catches bugs at compile time
function getUser(id: string): User {
return fetchUser(id); // Error: fetchUser expects number
// Bug found before runtime!
}
Type Narrowing
function processValue(value: string | number) {
// TypeScript knows value could be either
if (typeof value === 'string') {
// TypeScript knows value is string here
console.log(value.toUpperCase());
} else {
// TypeScript knows value is number here
console.log(value.toFixed(2));
}
}
Any Type as Red Flag
// ✗ Bad: Loses type safety
function process(data: any) {
return data.value; // No error checking
}
// ✓ Good: Proper typing
function process(data: { value: string }) {
return data.value; // TypeScript verifies structure
}
Debugging Checklist
When stuck, systematically check:
Environment:
- Correct Node version?
- Dependencies installed? (
npm install) - Environment variables set?
- Correct database/API URL?
Code:
- Can you reproduce reliably?
- What's the exact error message?
- What line throws the error?
- What are variable values at that point?
Data:
- Is data in expected format?
- Are fields null/undefined?
- Is array empty when expected full?
- Are types correct (string vs number)?
Network:
- API request succeeding?
- Correct URL and method?
- Auth headers present?
- Response status 200?
- Response body as expected?
State:
- Component mounted?
- Store initialized?
- User authenticated?
- Data loaded before access?
Portfolio Evidence
KSBs Demonstrated:
- S10: Analyse Problem Reports (systematic debugging approach)
- S11: Apply Appropriate Recovery Techniques (bug fixes)
- S13: Follow Testing Procedures (verification steps)
How to Document:
- Document bug investigation process
- Show before/after code
- Explain root cause analysis
- Include reproduction steps
- Show verification tests
- Screenshot DevTools usage
Evidence Example:
## Bug Investigation: Infinite Scroll Crash
**Problem**: Clicking "Load More" twice caused page freeze
**Reproduction Steps**:
1. Navigate to /feed
2. Click "Load More"
3. Click "Load More" again
4. Page freezes
**Investigation**:
- Console showed "Maximum call stack exceeded"
- Debugger breakpoint revealed offset not incrementing
- Isolated to `loadMoreItems()` function
**Root Cause**:
Offset state not updating correctly, causing same API call repeatedly
**Fix**:
```typescript
// Before
function loadMore() {
fetchItems(offset); // offset never changes
}
// After
function loadMore() {
fetchItems(offset);
offset += PAGE_SIZE; // Increment after fetch
}
```
**Verification**:
- Manual testing: ✓ Can click multiple times
- Added regression test
- No performance degradation
Root-Cause vs Symptom Fixes
Symptom fix: Fixes the immediate problem but doesn't prevent recurrence. Root-cause fix: Addresses the structural issue that allowed the bug to exist.
// Symptom fix: Add null check where the crash happens
const name = user?.profile?.name ?? 'Unknown';
// Root-cause fix: Ensure profile is always populated at creation
async function createUser(data: CreateUserRequest): Promise<User> {
return await db.users.create({
...data,
profile: { name: data.name } // Profile guaranteed at creation
});
}
When to ship a symptom fix: When the root cause is expensive to fix and the symptom fix is safe. But always log the root cause as a follow-up task.
When to insist on root-cause fix: When the bug pattern could recur in other places, when data integrity is at risk, or when the symptom fix introduces its own complexity.
Success Criteria
Debugging is effective when:
- Bugs reproduce reliably
- Root causes identified (not just symptoms)
- Fixes targeted and minimal
- No regressions introduced
- Process documented for learning
- Prevention strategies considered
- Structural patterns that enabled the bug are addressed or logged
Source
git clone https://github.com/JasonWarrenUK/claude-code-config/blob/main/skills/systematic-debugger/SKILL.mdView on GitHub Overview
Systematic Debugging provides a methodical approach to identifying and fixing software issues. It emphasizes reproducibility, isolation, and verification over trial-and-error, and covers browser DevTools, Node debugging, logging strategies, and Svelte-specific techniques.
How This Skill Works
It follows a four-step process—Reproduce, Isolate, Fix, Verify. Start by capturing reliable reproduction steps, then narrow the root cause with techniques like the Five Whys and minimal reproduction, implement a targeted fix, and finally verify the outcome with checks and re-running tests.
When to Use It
- User reports something not working
- Investigating errors or unexpected behavior
- Diagnosing performance issues
- Setting up debugging infrastructure
- Explaining or teaching debugging techniques
Quick Start
- Step 1: Reproduce the issue with reliable steps and document the exact behavior
- Step 2: Isolate the root cause using the Five Whys and a minimal repro
- Step 3: Implement a targeted fix and re-verify the issue is resolved
Best Practices
- Make the bug reproducible with reliable steps
- Document and share the exact reproduction case
- Apply the Five Whys to reach the root cause
- Use isolation techniques and a minimal reproduction
- Verify the fix by re-running the full repro and checks
Example Use Cases
- Debugging a dashboard bug where a second 'Load More' click freezes the page and logs an error
- Root-causing a stale data issue by identifying missing cache invalidation after an update
- Using a minimal reproduction to isolate a race condition in an async flow
- Applying DevTools and logging strategies to locate a rendering perf bottleneck
- Teaching a teammate a structured debugging approach with a clear reproduction and fix verification