Debugging
Systematic debugging framework with Makerkit-specific patterns.
Overview
Core principle: ALWAYS find root cause before attempting fixes. Symptom fixes are failure.
This skill orchestrates four sub-skills:
- systematic-debugging/ - Four-phase framework (mandatory)
- root-cause-tracing/ - Backward tracing technique
- verification-before-completion/ - Evidence gates
- defense-in-depth/ - Validation layers after fix
Quick Start
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
The Four Phases:
- Root Cause - Read errors, reproduce, check changes, gather evidence
- Pattern - Find working examples, compare, identify differences
- Hypothesis - Form theory, test minimally, verify
- Implementation - Create failing test, fix root cause, verify
If 3+ fixes failed: Question the architecture, not another fix.
Makerkit-Specific Debugging
Supabase Issues
RLS Policy Failures
# Check if RLS is the issue
pnpm supabase:web:start # Start local Supabase
# In Supabase Studio (localhost:54323):
# 1. SQL Editor → Run query with service_role (bypasses RLS)
# 2. If works with service_role but not anon/authenticated → RLS issue
# Debug specific policy
SELECT * FROM pg_policies WHERE tablename = 'your_table';
# Test policy logic
SET LOCAL ROLE authenticated;
SET LOCAL request.jwt.claims = '{"sub": "user-uuid-here"}';
SELECT * FROM your_table;
Common RLS issues:
- Missing
auth.uid()check in policy - Policy using wrong column for user ownership
- Missing policy for specific operation (SELECT exists but INSERT missing)
Migration Conflicts
# Reset local DB and reapply migrations
pnpm supabase:web:reset
# If migration fails, check:
# 1. supabase/migrations/ folder for conflicting files
# 2. Order of migrations (timestamps)
# 3. Dependencies between migrations
# Generate diff to see pending changes
pnpm supabase:web:diff
Type Sync Issues (Supabase ↔ Drizzle)
# Regenerate Supabase types
pnpm supabase:web:typegen
# Pull schema into Drizzle
pnpm drizzle:pull
# If types mismatch:
# 1. Check supabase/schemas/*.sql matches actual DB
# 2. Verify packages/supabase/src/ reflects latest schema
# 3. Run both commands in sequence
Drizzle ORM Issues
Schema Drift
# Compare Drizzle schema vs actual DB
# 1. Check supabase/schemas/ folder
# 2. Compare with packages/supabase/src/schema/
# Pull fresh from DB
pnpm drizzle:pull
# If conflict: Trust supabase/schemas/ as source of truth
Query Builder Errors
// Common issue: Wrong import
// ❌ Wrong
import { eq } from 'drizzle-orm';
import { users } from '@kit/supabase';
// ✅ Correct
import { eq } from 'drizzle-orm';
import { users } from '@kit/supabase/schema';
Type Inference Issues
// If Drizzle types are wrong after schema change:
// 1. pnpm supabase:web:typegen
// 2. pnpm drizzle:pull
// 3. Restart TypeScript server (VS Code: Cmd+Shift+P → "Restart TS Server")
Next.js App Router Issues
Server Component Errors
Error: You're importing a component that needs useState/useEffect...
Fix: Add "use client" directive to the component or its parent.
Debug strategy:
# Find the boundary
# 1. Error shows which component needs client
# 2. Check if it uses: useState, useEffect, useContext, event handlers
# 3. Add "use client" at the top of that file
Hydration Mismatches
Warning: Text content did not match. Server: "..." Client: "..."
Common causes:
- Date/time rendering (server vs client timezone)
- Random values generated on render
- Browser-only APIs used during SSR
- Extension injecting content
Fix pattern:
// Use useEffect for client-only values
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
if (!mounted) return <Skeleton />;
Route Conflicts
apps/web/app/
├── (marketing)/ ← Route group (no URL segment)
├── [account]/ ← Dynamic segment
└── admin/ ← Static segment
Priority: Static > Dynamic > Catch-all
Debug:
# Check for conflicting routes
ls -la apps/web/app/
# Common issues:
# 1. (group) folder accidentally has page.tsx
# 2. [dynamic] conflicts with static route
# 3. [...catchAll] catches too much
Server Actions Failing
// Check enhanceAction wrapper
export const myAction = enhanceAction(async (data) => {
// If fails silently, add logging:
console.log('Action input:', data);
// Check auth context
const { user } = await requireUser();
console.log('User:', user?.id);
}, {
schema: mySchema,
});
Turborepo Issues
Cache Invalidation
# Force rebuild without cache
turbo run build --force
# Clear all caches
pnpm clean
# Check what's cached
turbo run build --dry-run
Task Dependency Issues
// turbo.json - Check dependsOn
{
"tasks": {
"build": {
"dependsOn": ["^build"], // ← Depends on workspace deps
"outputs": [".next/**", "dist/**"]
}
}
}
Debug:
# See task graph
turbo run build --graph
# Run specific package
turbo run build --filter=web
Parallel Task Failures
# If parallel tasks fail randomly:
# 1. Check for shared state (files, ports)
# 2. Run sequentially to isolate
turbo run build --concurrency=1
Stripe Integration Issues
Webhook Failures
# Start Stripe webhook listener
pnpm stripe:listen
# Check webhook logs in Stripe Dashboard
# Common issues:
# 1. Wrong webhook secret in .env
# 2. Event type not handled
# 3. Handler throws before acknowledging
Debug pattern:
// In webhook handler, always acknowledge first
export async function POST(request: Request) {
const body = await request.text();
const sig = request.headers.get('stripe-signature');
try {
const event = stripe.webhooks.constructEvent(body, sig!, webhookSecret);
// Acknowledge immediately
// Process async if needed
switch (event.type) {
case 'checkout.session.completed':
// Handle...
break;
}
return new Response('OK', { status: 200 });
} catch (err) {
console.error('Webhook error:', err);
return new Response('Webhook Error', { status: 400 });
}
}
Debugging Workflow
Phase 1: Root Cause Investigation
BEFORE any fix:
Read error messages carefully
- Stack traces completely
- Line numbers, file paths
- Don't skip warnings
Reproduce consistently
- Exact steps?
- Every time or intermittent?
Check recent changes
git diff HEAD~5 git log --oneline -10Gather evidence in multi-component systems
For EACH component boundary: - Log what enters - Log what exits - Verify env/config
Reference: systematic-debugging/SKILL.md
Phase 2: Pattern Analysis
- Find working examples in same codebase
- Compare against references (read completely, don't skim)
- Identify differences (list all, don't assume "can't matter")
Phase 3: Hypothesis Testing
- Form single hypothesis: "I think X because Y"
- Test minimally: Smallest possible change
- Verify: Yes → Phase 4, No → New hypothesis
Phase 4: Implementation
- Create failing test (if possible)
- Implement single fix (no "while I'm here")
- Verify fix (tests pass, no regressions)
- If 3+ fixes failed: Question the architecture
Quick Reference Commands
Makerkit Stack
# Database
pnpm supabase:web:start # Start local Supabase
pnpm supabase:web:reset # Reset and remigrate
pnpm supabase:web:typegen # Regenerate types
pnpm supabase:web:diff # See pending changes
# Drizzle
pnpm drizzle:pull # Pull schema from DB
pnpm drizzle:studio # Open Drizzle Studio
# Build
pnpm typecheck # TypeScript check
pnpm lint # ESLint
pnpm build # Full build
turbo run build --force # Force rebuild
# Stripe
pnpm stripe:listen # Start webhook listener
Red Flags - STOP
If you catch yourself:
- "Quick fix for now, investigate later"
- "Just try changing X and see"
- "I don't fully understand but this might work"
- Proposing solutions before tracing data flow
- Already tried 2+ fixes → Question architecture
ALL of these mean: STOP. Return to Phase 1.
Sub-Skills Reference
| Sub-Skill | When to Use | Link |
|---|---|---|
| systematic-debugging | Any bug - start here | systematic-debugging/SKILL.md |
| root-cause-tracing | Deep in call stack | root-cause-tracing/SKILL.md |
| verification-before-completion | Before claiming "fixed" | verification-before-completion/SKILL.md |
| defense-in-depth | Adding validation after fix | defense-in-depth/SKILL.md |
Success Criteria
A debugging session is complete when:
- Root cause identified (not just symptom)
- Fix addresses root cause
- Tests pass (new + existing)
- No regressions introduced
- Evidence gathered before claiming success