debugger
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: 1. [Action 1] 2. [Action 2] 3. [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:
1. Start at error location
2. Trace backward to find data source
3. Trace forward to find impact
4. 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-testidattributes 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
1. [Step 1]
2. [Step 2]
3. [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:
awaitin 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.