ワンクリックで
systematic-debugging
// Use when debugging failures, errors, or unexpected behavior. Covers root cause investigation, data flow tracing, hypothesis-driven debugging, and fix verification to prevent trial-and-error approaches.
// Use when debugging failures, errors, or unexpected behavior. Covers root cause investigation, data flow tracing, hypothesis-driven debugging, and fix verification to prevent trial-and-error approaches.
Use when starting isolated feature work or before executing implementation plans. Manages full worktree lifecycle from creation through cleanup with safety checks and error recovery.
Configuration reference and troubleshooting for the statusline plugin — sections, themes, bar widths, and script architecture
Common agent patterns and templates for Claude Code. Use when implementing agents to follow proven patterns for Tasks integration, quality checks, and external model invocation via claudish CLI.
CRITICAL - Guide for using Claudish CLI ONLY through sub-agents to run Claude Code with OpenRouter models (Grok, GPT-5, Gemini, MiniMax). NEVER run Claudish directly in main context unless user explicitly requests it. Use when user mentions external AI models, Claudish, OpenRouter, or alternative models. Includes mandatory sub-agent delegation patterns, agent selection guide, file-based instructions, and strict rules to prevent context window pollution.
CRITICAL - Guide for using Claudish CLI ONLY through sub-agents to run Claude Code with OpenRouter models (Grok, GPT-5, Gemini, MiniMax). NEVER run Claudish directly in main context unless user explicitly requests it. Use when user mentions external AI models, Claudish, OpenRouter, or alternative models. Includes mandatory sub-agent delegation patterns, agent selection guide, file-based instructions, and strict rules to prevent context window pollution.
Prompting patterns and review templates for UI design analysis with Gemini multimodal capabilities. Use when conducting design reviews, accessibility audits, or design system validation.
| name | systematic-debugging |
| description | Use when debugging failures, errors, or unexpected behavior. Covers root cause investigation, data flow tracing, hypothesis-driven debugging, and fix verification to prevent trial-and-error approaches. |
| keywords | ["debugging","root-cause","trace-data-flow","divergence","hypothesis","fix-verification","stack-trace","error-analysis","profiling","logging","TypeError","null-undefined","race-condition","promise-not-awaited","validation-failed","wrong-condition","env-var","stale-cache","format-changed","early-return","service-not-running"] |
| created | "2026-01-20T00:00:00.000Z" |
| updated | "2026-01-20T00:00:00.000Z" |
| plugin | dev |
| type | discipline |
| difficulty | beginner |
Iron Law: "NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST"
Use this skill when:
Detect these patterns that indicate skipping root cause investigation:
Symptom: What you observe (test fails, error thrown, wrong output) Root Cause: Why it happens (null value, wrong condition, missing await)
Example:
Symptom: "TypeError: Cannot read property 'name' of undefined"
Root Cause: API returns null when user not found, but code expects object
Bad approach: Add user?.name (fixes symptom, not cause)
Good approach: Add validation if (!user) throw new NotFoundError() (fixes cause)
Principle: Follow data from source to error point
Steps:
Example:
Error: "Expected 'active' but got 'inactive'"
Location: user.test.ts:42 - expect(user.status).toBe('active')
Data: user.status = 'inactive'
Trace: user.status ← updateUser() ← API response ← database
Divergence: Database has status='inactive' (expected 'active')
Root Cause: Test setup didn't create user with active status
Principle: Form hypothesis, test with evidence, refine
Process:
Example:
Symptom: API request times out after 30s
Hypothesis 1: Database query is slow
Prediction: Should see long query time in logs
Test: Add query timing logs
Result: Queries complete in <100ms ✗ Hypothesis rejected
Hypothesis 2: Network connection is hanging
Prediction: Should see connection delay, not query delay
Test: Add request timing logs (connect time vs. query time)
Result: Connection takes 31s, query never runs ✓ Hypothesis confirmed
Root Cause: Firewall blocks connection, causing timeout
Principle: Verify fix addresses root cause, not just symptom
Checklist:
Objective: Identify where actual diverges from expected
Steps:
Example (TypeScript):
// Error: "Expected user email, got undefined"
// Stack trace: user-service.ts:42
// Phase 1: Trace data flow
console.log('1. API response:', response); // { data: { user: {...} } }
console.log('2. Extracted user:', response.data); // { user: {...} }
console.log('3. User object:', response.data.user); // { id: 1, name: 'Alice' }
console.log('4. Email field:', response.data.user.email); // undefined
// Divergence found: response.data.user has no email field
Objective: Determine why actual differs from expected
Questions:
Example (Python):
# Expected: parse_csv() returns list of dicts with 'email' key
# Actual: parse_csv() returns list of dicts without 'email' key
# Check input CSV file
with open('users.csv') as f:
print(f.readline()) # id,name,phone ← Missing 'email' column!
# Divergence: CSV file format changed, missing 'email' column
Objective: Form testable hypothesis about why divergence occurred
Hypothesis Template:
"I believe [divergence] occurs because [root cause].
If this is true, I should see [evidence].
I can test this by [action]."
Example (Go):
// Divergence: user.Email is empty string when fetched from cache
// Hypothesis 1: Cache serialization drops empty fields
// Evidence: Other empty fields (phone, address) also missing
// Test: Check cached JSON structure
// Result: {"id":1,"name":"Alice"} ← Empty fields missing ✓
// Root Cause: JSON serialization omits empty fields (omitempty tag)
Objective: Confirm fix addresses root cause
Verification Steps:
Example (TypeScript):
// Root Cause: JSON serialization omits fields with undefined values
// Before fix:
JSON.stringify({ id: 1, email: undefined }) // {"id":1}
// Fix: Filter out undefined before serialization
const filtered = Object.fromEntries(
Object.entries(user).filter(([_, v]) => v !== undefined)
);
// Verification:
expect(filtered).toEqual({ id: 1 }); // ✓ Correct behavior
expect(JSON.stringify(filtered)).toBe('{"id":1}'); // ✓ Serialized correctly
Strategy: Identify assertion, trace data, find divergence
// Test fails: expect(result).toBe(5)
test('calculates total', () => {
const result = calculateTotal([1, 2, 2]);
console.log('Input:', [1, 2, 2]); // Input data
console.log('Expected:', 5); // Expected result
console.log('Actual:', result); // Actual result (6)
console.log('Divergence:', result - 5); // Difference (1)
expect(result).toBe(5);
});
// Trace: calculateTotal() sums array incorrectly
// Root Cause: Off-by-one error in loop (includes index 0 twice)
Strategy: Profile execution, identify bottleneck
import time
def slow_function():
start = time.time()
# Phase 1: Identify slow section
data = fetch_data() # 0.1s
print(f"Fetch: {time.time() - start:.2f}s")
processed = process_data(data) # 5.2s ← Bottleneck!
print(f"Process: {time.time() - start:.2f}s")
save_data(processed) # 0.05s
print(f"Save: {time.time() - start:.2f}s")
# Root Cause: process_data() has O(n²) algorithm
Strategy: Trace data mutations, find unexpected write
// Symptom: User email changes unexpectedly
// Phase 1: Add logging to all mutation points
func UpdateUser(user *User) {
log.Printf("Before: %+v", user)
user.Email = normalizeEmail(user.Email)
log.Printf("After normalize: %+v", user)
db.Save(user)
log.Printf("After save: %+v", user)
}
// Logs show: Email changes in normalizeEmail()
// Root Cause: normalizeEmail() lowercases domain incorrectly
Strategy: Read stack trace, identify throw location, trace backwards
// Error: "TypeError: Cannot read property 'length' of null"
// Stack trace:
// at validateInput (validator.ts:12)
// at handleSubmit (form.ts:45)
// at onClick (button.tsx:8)
// Phase 1: Find throw location (validator.ts:12)
function validateInput(input: string) {
if (input.length < 3) { // ← Line 12, input is null
throw new Error('Too short');
}
}
// Phase 2: Trace backwards (form.ts:45)
function handleSubmit() {
const input = getInputValue(); // Returns null when field empty
validateInput(input); // ← Passes null to validateInput
}
// Root Cause: getInputValue() returns null, but validateInput expects string
// Fix: Add null check or change return type to empty string
Strategy: Identify last working state, compare changes
# Find last working commit
git bisect start
git bisect bad HEAD # Current state (broken)
git bisect good v1.2.0 # Last known working version
# Git bisect identifies commit abc123 as first bad commit
git show abc123 # Shows changes
# Root Cause: Commit abc123 changed API response format
Symptom:
// Test fails: "Expected button to be disabled"
test('disables submit when invalid', () => {
render(<Form />);
const button = screen.getByRole('button');
expect(button).toBeDisabled(); // ✗ Fails, button is enabled
});
Phase 1: Trace Data Flow
// Add logging to Form component
function Form() {
const [isValid, setIsValid] = useState(false);
console.log('isValid:', isValid); // false (expected)
return (
<button disabled={!isValid}>Submit</button>
// disabled={!false} → disabled={true} → Button should be disabled
);
}
Phase 2: Identify Divergence
// Check actual DOM state
const button = screen.getByRole('button');
console.log('Disabled attribute:', button.disabled); // false (actual)
console.log('Button HTML:', button.outerHTML);
// <button>Submit</button> ← Missing 'disabled' attribute!
Phase 3: Hypothesize Root Cause
// Hypothesis: disabled={!isValid} is not setting attribute
// Evidence: Check if React is updating DOM correctly
// Test: Add explicit disabled={true} to verify React works
return <button disabled={true}>Submit</button>;
// Result: Button is NOW disabled ✓
// Conclusion: disabled={!isValid} expression is wrong
Phase 4: Verify Fix
// Root Cause: !isValid evaluates to true, but disabled expects boolean
// Wait... that IS boolean. Let me re-check the initial state.
// Re-trace: Where is isValid initialized?
const [isValid, setIsValid] = useState(false); // ✓ Correct
// Check component rendering
console.log('Rendering with isValid:', isValid);
// Logs: "Rendering with isValid: undefined" ← FOUND IT!
// Root Cause: useState(false) runs AFTER first render in test
// Fix: Set initial state before render (or use initialProps)
test('disables submit when invalid', () => {
render(<Form initialValid={false} />); // ✓ Now works
});
Symptom:
# API request times out after 30 seconds
response = requests.get('https://api.example.com/users')
# Raises: requests.exceptions.Timeout
Phase 1: Trace Data Flow
import time
start = time.time()
try:
response = requests.get('https://api.example.com/users', timeout=30)
print(f"Request took: {time.time() - start:.2f}s")
except requests.exceptions.Timeout:
print(f"Timeout after: {time.time() - start:.2f}s") # 30.01s
Phase 2: Identify Divergence
# Expected: Request completes in <1s (normal API response time)
# Actual: Request times out at 30s
# Divergence: Something delays request for 30+ seconds
# Hypothesis: Network issue, DNS resolution, or connection hang
# Test: Add connection timing
response = requests.get(
'https://api.example.com/users',
timeout=(5, 30) # (connect timeout, read timeout)
)
# Result: Raises timeout after 5s ← Connection timeout!
Phase 3: Hypothesize Root Cause
# Hypothesis: DNS resolution fails or connection refused
# Test: Try IP address instead of domain
response = requests.get('http://192.168.1.100/users')
# Result: Works! ✓
# Root Cause: DNS resolution for api.example.com fails
# Verification: Check DNS
import socket
socket.gethostbyname('api.example.com') # Raises: gaierror (DNS failure)
Phase 4: Verify Fix
# Fix: Use IP address or fix DNS configuration
# Updated code:
API_HOST = os.getenv('API_HOST', '192.168.1.100')
response = requests.get(f'http://{API_HOST}/users')
# Verification:
assert response.status_code == 200 # ✓ Works
assert response.elapsed.total_seconds() < 1 # ✓ Fast
Symptom:
// User email is corrupted after update
user := User{ID: 1, Email: "Alice@Example.com"}
UpdateUser(&user)
fmt.Println(user.Email) // Prints: "alice@example.com" (unexpected lowercase)
Phase 1: Trace Data Flow
func UpdateUser(user *User) {
log.Printf("Before: %+v", user) // Email: "Alice@Example.com"
user.Email = normalizeEmail(user.Email)
log.Printf("After normalize: %+v", user) // Email: "alice@example.com"
db.Save(user)
log.Printf("After save: %+v", user) // Email: "alice@example.com"
}
// Divergence: normalizeEmail() changes case
Phase 2: Identify Divergence
// Expected: Email preserves original case
// Actual: Email is lowercased
// Divergence: normalizeEmail() function
func normalizeEmail(email string) string {
return strings.ToLower(email) // ← Root cause!
}
Phase 3: Hypothesize Root Cause
// Hypothesis: normalizeEmail() should only lowercase domain, not entire email
// Expected behavior: Alice@Example.com → Alice@example.com
// Test: Check email RFC standards
// Verification: Email local part (before @) is case-sensitive
// Email domain (after @) is case-insensitive
// Root Cause: normalizeEmail() lowercases entire email, should only lowercase domain
Phase 4: Verify Fix
// Fix: Only lowercase domain part
func normalizeEmail(email string) string {
parts := strings.Split(email, "@")
if len(parts) != 2 {
return email // Invalid email, return as-is
}
return parts[0] + "@" + strings.ToLower(parts[1])
}
// Verification:
assert.Equal(t, "Alice@example.com", normalizeEmail("Alice@Example.com"))
assert.Equal(t, "alice@example.com", normalizeEmail("alice@EXAMPLE.COM"))
After debugging and fixing, use verification-before-completion to confirm:
When debugging reveals a bug:
For complex debugging requiring multiple investigations:
Before marking debugging task complete:
If any checkbox is unchecked, continue debugging. Do not apply fix until root cause is understood.