skills/debugging/SKILL.md
Systematic debugging methodology — runtime errors, test failures, logic bugs, performance issues, production incidents. Five-step framework, root-cause analysis, browser/Node/Svelte tooling, and common bug patterns.
npx skillsauth add jasonwarrenuk/goblin-mode debuggingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Methodical approach to identifying and fixing software issues. Emphasises reproducibility, isolation, and verification over trial-and-error debugging. Covers browser DevTools, Node debugging, logging strategies, and Svelte-specific debugging techniques.
Use this skill when:
Five-step process: Reproduce → Isolate → Diagnose → Fix → Verify
This applies to every debugging scenario without exception.
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:
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:
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 judgement:
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!
Understand why it's happening
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 initialisation:
// 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');
}
Confirm the fix works
Don't assume it's fixed. Test thoroughly.
Verification checklist:
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);
});
Projects use different runtimes (npm, bun, deno). Always detect before running commands:
if [ -f "bun.lockb" ]; then
# bun project
elif [ -f "deno.json" ] || [ -f "deno.lock" ]; then
# deno project
else
# npm/pnpm/yarn project
fi
Deno:
deno test --watch path/to/test.ts
deno run --inspect-brk script.ts # Debug with Chrome DevTools
Bun:
bun test --watch path/to/test.ts
bun --inspect script.ts # Debug
bun --hot script.ts # Hot reload
npm:
npm test -- --watch path/to/test.ts
node --inspect-brk script.js # Debug
Strategic logging:
// ✗ Bad: Non-informative
console.log(data);
// ✓ Good: Labelled 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
Inspect API requests:
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
Inspect DOM:
Common CSS issues:
/* Check computed styles for: */
- Display: none (hidden element)
- Z-index conflicts
- Overflow: hidden (content clipped)
- Position issues
- Flexbox/Grid properties
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:
Debugger controls:
Inspect storage:
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
Profile performance:
Look for:
Start with inspect:
node --inspect server.js
# or
node --inspect-brk server.js # Break on first line
Chrome DevTools:
// 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;
}
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
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>
<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>
import { writable } from 'svelte/store';
function createDebugStore(name, initial) {
const store = writable(initial);
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);
Browser extension - Install Svelte DevTools
Features:
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
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);
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 });
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);
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);
}
</script>
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';
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>
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;
}
Chrome DevTools Memory Tab:
// TypeScript catches bugs at compile time
function getUser(id: string): User {
return fetchUser(id); // Error: fetchUser expects number
// Bug found before runtime!
}
function processValue(value: string | number) {
if (typeof value === 'string') {
console.log(value.toUpperCase());
} else {
console.log(value.toFixed(2));
}
}
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.
Before:
During:
After:
development
Writing style guide for Jason Warren. Use this skill whenever writing prose, reports, documentation, or any substantive text for Jason — including drafting sections, editing existing content, or rewriting passages. Also use when Jason asks you to review or improve writing. Trigger on any request involving writing, drafting, editing, or composing text that isn't purely code. This includes github Pull Requests & Linear tasks
testing
{{ 𝚫𝚫𝚫 }} Check all version number props and update them
tools
{{ 𝛀𝛀𝛀 }} Map out project status, direction, and next steps
development
{{ 𝛀𝛀𝛀 }} Review code changes on the current branch against its open PR