Debugger Skill
Systematically diagnose and fix software bugs through structured investigation, reproduction, root cause analysis, and targeted fixes.
When to Use
Use this skill for:
-
Runtime Errors: Exception handling, crashes, unhandled rejections
-
Test Failures: Unit tests, integration tests, E2E tests not passing
-
Type Errors: TypeScript compilation errors, type mismatches
-
Logic Bugs: Incorrect behavior, wrong output, edge cases
-
Performance Issues: Slow execution, memory leaks, bottlenecks
-
Integration Issues: API failures, database errors, third-party service issues
-
Race Conditions: Timing issues, async/await problems
-
State Management: Redux, Context, or hook state bugs
Don't use for:
-
New feature implementation (use feature-implementer)
-
Code refactoring without bugs (use refactorer)
-
Initial research (use Explore agent)
Quick Start
Step 1: Issue Understanding
What: [Brief description of the bug] Where: [File(s) and line number(s)] When: [Conditions when it occurs] Impact: [Severity: Critical/High/Medium/Low] Evidence: [Error message, stack trace, or behavior]
Step 2: Reproduce
Create minimal reproduction:
-
Identify exact steps to trigger
-
Document environment conditions
-
Capture before/after state
-
Note any workarounds
Step 3: Investigate
Use systematic approach:
-
Read relevant code
-
Trace execution flow
-
Check data values at key points
-
Verify assumptions
Step 4: Root Cause
Identify the fundamental issue:
-
Why did it happen?
-
What was the incorrect assumption?
-
What edge case was missed?
Step 5: Fix
Implement targeted solution:
-
Minimal change to fix root cause
-
Add safeguards against regression
-
Update tests if needed
Step 6: Validate
Verify the fix:
-
Original reproduction no longer fails
-
Tests pass
-
No new issues introduced
-
Edge cases covered
Debugging Methodology
Phase 1: Gather Evidence
What to collect:
-
Error messages (complete text)
-
Stack traces (full trace, not truncated)
-
Log output (with timestamps)
-
State snapshots (variables, props, store)
-
Environment details (OS, browser, Node version)
Tools:
-
Logger service output
-
Browser DevTools console
-
Test runner output (Vitest, Playwright)
-
TypeScript compiler errors
-
Build output
Commands:
Run with verbose output
npm run test -- --reporter=verbose
TypeScript with detailed errors
npx tsc --noEmit --pretty
Build with stack traces
npm run build 2>&1 | tee build.log
Phase 2: Reproduce Reliably
Create minimal reproduction:
Isolate the issue
-
Remove unrelated code
-
Use minimal data
-
Simplify conditions
Document steps
Reproduction Steps:
- [Action 1]
- [Action 2]
- [Action 3] Expected: [What should happen] Actual: [What actually happens]
Verify consistency
-
Try 3-5 times
-
Note any variations
-
Check different environments
Phase 3: Form Hypotheses
Ask systematic questions:
-
Is this a logic error, type error, or runtime error?
-
Is it a timing issue (race condition)?
-
Is it data-dependent?
-
Is it environment-specific?
-
Is it a regression (was it working before)?
Common root causes:
-
Incorrect assumptions about data shape
-
Missing null/undefined checks
-
Async/await misuse
-
Type coercion issues
-
Off-by-one errors
-
State mutation issues
-
Incorrect error handling
-
Missing dependencies in useEffect
Phase 4: Investigate
Read relevant code:
- Start at error location
- Trace backward to find data source
- Trace forward to find impact
- Check related components/functions
Add logging (temporarily):
// Use Logger service, not console.log import { logger } from '@/lib/logging/logger';
logger.debug('Variable state', { component: 'ComponentName', value: myVar, type: typeof myVar, });
Use debugger statements (for browser):
// Temporary debugging if (condition) { debugger; // Browser will pause here }
Phase 5: Root Cause Analysis
Identify the true cause:
Not just: "Variable is undefined" But: "Function assumes input always has .data property, but API can return null on empty results"
Not just: "Test times out" But: "Async function doesn't resolve because mock doesn't implement .then() method"
Not just: "Type error on line 42" But: "Function returns Promise<T> but caller expects T because it's not awaiting"
Causal chain:
Root cause: [Fundamental issue] ↓ Proximate cause: [Immediate trigger] ↓ Symptom: [Observed error]
Phase 6: Implement Fix
Fix principles:
Targeted: Address root cause, not symptoms
// ❌ Bad: Suppress symptom try { doThing(); } catch { /* ignore */ }
// ✅ Good: Fix root cause if (data?.hasProperty) { doThing(); }
Minimal: Smallest change that fixes issue
-
Don't refactor unrelated code
-
Don't add unnecessary features
-
Keep scope focused
Defensive: Add safeguards
// Add validation if (!data || !data.items) { logger.warn('Invalid data structure', { data }); return []; }
Tested: Ensure it works
-
Original reproduction passes
-
Add test for regression prevention
-
Verify edge cases
Common fix patterns:
Null/undefined handling:
// ❌ Before const result = data.items[0].value;
// ✅ After const result = data?.items?.[0]?.value ?? defaultValue;
Async/await:
// ❌ Before useEffect(() => { const data = await fetchData(); setData(data); }, []);
// ✅ After useEffect(() => { const loadData = async () => { const data = await fetchData(); setData(data); }; void loadData(); }, []);
Type errors:
// ❌ Before function process(value: string | null): string { return value.toUpperCase(); // Error: Object is possibly null }
// ✅ After function process(value: string | null): string { return value?.toUpperCase() ?? ''; }
Phase 7: Validate Fix
Validation checklist:
-
Original error no longer occurs
-
Tests pass (all, not just related ones)
-
Build succeeds
-
Lint passes
-
No new errors introduced
-
Edge cases work
-
Performance not degraded
Run validation:
Full validation
npm run lint && npm run test && npm run build
Debugging Patterns by Issue Type
TypeScript Errors
Common issues:
-
Type mismatch
-
Property doesn't exist
-
Object is possibly undefined
-
Await outside async function
-
Argument count mismatch
Debugging approach:
-
Read error message carefully (TypeScript errors are descriptive)
-
Check type definitions with Cmd+Click (VS Code)
-
Verify actual type vs expected type
-
Look for missing null checks
-
Check async/await usage
Example fix:
// Error: Property 'name' does not exist on type 'User | null' const userName = user.name;
// Fix: Add null check const userName = user?.name ?? 'Anonymous';
Test Failures
Common issues:
-
Mock not configured correctly
-
Async operation not awaited
-
Test isolation issues (shared state)
-
Timeout (async not resolving)
-
Assertion mismatch
Debugging approach:
-
Read test output carefully
-
Run single test in isolation
-
Check mock setup
-
Verify async operations resolve
-
Add console logging (temporarily)
Example fix:
// ❌ Test failing: Mock not properly awaitable vi.mock('@/lib/service', () => ({ fetchData: vi.fn(() => ({ data: 'test' })), }));
// ✅ Fix: Return promise vi.mock('@/lib/service', () => ({ fetchData: vi.fn(() => Promise.resolve({ data: 'test' })), }));
Runtime Errors
Common issues:
-
Uncaught exception
-
Unhandled promise rejection
-
Cannot read property of undefined
-
Function not defined
-
Network request failure
Debugging approach:
-
Check stack trace for error origin
-
Verify data shape at error point
-
Add defensive checks
-
Check for race conditions
-
Verify dependencies loaded
Example fix:
// ❌ Error: Cannot read property 'length' of undefined const count = items.length;
// ✅ Fix: Add defensive check const count = items?.length ?? 0;
Performance Issues
Common issues:
-
Infinite loop
-
Unnecessary re-renders
-
Memory leak
-
Large data processing
-
Blocking operations
Debugging approach:
-
Use React DevTools Profiler
-
Check useEffect dependencies
-
Look for missing memoization
-
Profile with browser DevTools
-
Check for cleanup functions
Example fix:
// ❌ Performance issue: Infinite re-render useEffect(() => { setData(processData(data)); }, [data]); // Triggers on own update!
// ✅ Fix: Remove circular dependency useEffect(() => { setData(processData(initialData)); }, [initialData]);
Race Conditions
Common issues:
-
State update after unmount
-
Multiple async operations
-
Callback with stale closure
-
Event handler timing
Debugging approach:
-
Check async operation lifecycle
-
Add cleanup functions
-
Use refs for latest values
-
Add abort controllers
Example fix:
// ❌ Race condition: Update after unmount useEffect(() => { fetchData().then(data => setData(data)); }, []);
// ✅ Fix: Add cleanup useEffect(() => { let mounted = true; fetchData().then(data => { if (mounted) setData(data); }); return () => { mounted = false; }; }, []);
Project-Specific Debugging
Novelist.ai Specifics
Logger Service (REQUIRED):
import { logger } from '@/lib/logging/logger';
// ❌ Never use console.log console.log('Debug:', value);
// ✅ Always use logger logger.debug('Debug message', { component: 'ComponentName', value, });
Database Debugging:
// Check Turso connection logger.debug('Database query', { component: 'ServiceName', table: 'table_name', params, });
Test Debugging:
-
Vitest for unit tests
-
Playwright for E2E tests
-
Use data-testid attributes for selectors
Common Novelist.ai Issues:
-
LocalStorage vs Turso sync issues
-
Device ID generation in tests
-
Plot Engine state management
-
World Building data relationships
Advanced Debugging
Binary Search Debugging
For complex bugs, use binary search:
-
Find known good state (e.g., working commit)
-
Find known bad state (e.g., current broken state)
-
Check midpoint
-
Narrow down until isolated
Git bisect for regressions
git bisect start git bisect bad HEAD git bisect good <known-good-commit>
Git will checkout midpoint
Test and mark good/bad until found
Heisenbug (Disappears when debugging)
Strategies:
-
Add non-invasive logging
-
Record state snapshots
-
Check for timing dependencies
-
Look for race conditions
-
Test in production mode
Rubber Duck Debugging
When stuck:
-
Explain the problem out loud (or in writing)
-
Describe what the code does line-by-line
-
State your assumptions explicitly
-
Often reveals the issue
Best Practices
DO:
✓ Read error messages completely ✓ Create minimal reproductions ✓ Form hypotheses before changing code ✓ Fix root cause, not symptoms ✓ Add tests for regressions ✓ Use Logger service (not console.log) ✓ Validate fixes thoroughly ✓ Document complex bugs
DON'T:
✗ Guess randomly ("try-and-see" debugging) ✗ Make multiple changes at once ✗ Skip reproduction step ✗ Fix symptoms without understanding cause ✗ Leave debug code in production ✗ Ignore test failures ✗ Over-complicate fixes
Output Format
When completing debugging, provide:
Debug Report: [Issue Title]
Issue Summary
- What: [Brief description]
- Where: [File:line]
- Severity: [Critical/High/Medium/Low]
Symptoms
- [Observed error or behavior]
- [Stack trace or error message]
Reproduction Steps
- [Step 1]
- [Step 2]
- [Result]
Root Cause
[Explanation of fundamental issue]
Solution
[Description of fix applied]
Files Modified:
- [File path 1] - [What was changed]
- [File path 2] - [What was changed]
Validation
- [✓] Original issue resolved
- [✓] Tests passing
- [✓] Build succeeds
- [✓] No regressions
Prevention
[How to avoid this in the future]
Examples
Example 1: Async/Await Error
Issue: Build fails with "await outside async function"
Investigation:
-
Read error: Line 140 in useWritingAssistant.ts
-
Found: await in useEffect callback
-
Root cause: useEffect callbacks can't be async
Fix:
// Before useEffect(() => { const data = await loadData(); setData(data); }, []);
// After useEffect(() => { const load = async () => { const data = await loadData(); setData(data); }; void load(); }, []);
Validation: Build passes, tests pass ✓
Example 2: Test Timeout
Issue: Test times out after 10 seconds
Investigation:
-
Test calls async function
-
Mock returns object, not promise
-
Test awaits forever
Root cause: Mock missing .then() method
Fix:
// Before mock.fn(() => ({ data: 'test' }));
// After mock.fn(() => Promise.resolve({ data: 'test' }));
Validation: Test passes in <100ms ✓
Example 3: Type Error
Issue: Property 'items' doesn't exist on type 'Response | null'
Investigation:
-
API can return null
-
Code assumes always returns object
-
No null check
Root cause: Missing null handling
Fix:
// Before const items = response.items;
// After const items = response?.items ?? [];
Validation: TypeScript compiles, runtime safe ✓
Integration with Other Skills
-
iterative-refinement: For fixing multiple bugs in cycles
-
goap-agent: For coordinating complex debugging across multiple files
-
test-runner: For validating fixes
-
code-reviewer: For reviewing fix quality
Tools Available
This skill has access to:
-
Read: Read source files
-
Grep: Search for patterns
-
Glob: Find files
-
Edit: Fix bugs
-
Bash: Run tests, build, validate
Use these tools systematically to diagnose and fix issues.