Debugging Automation: A Claude Code Workflow for Systematically Finding and Fixing Bugs
One-Line Summary
From "intermittently failing bugs" to "errors that can't be reproduced," combine the investigate + systematic-debugging + test-driven-development skills to find root causes and prevent regressions.
Target Audience
- Students who have copied error messages into Google and just followed Stack Overflow answers
- Team members tired of "whack-a-mole" situations where fixed bugs reappear the next day
- Developers who need to debug intermittent errors occurring in Supabase / Next.js combinations
- Team leaders who need to document the debugging process and share it with team members
Core Workflow
Full Debugging Cycle
[Bug discovered]
↓
investigate → Understand the symptom + form hypothesis
↓
systematic-debugging → Verify hypotheses + confirm root cause
↓
[Write fix]
↓
verification-before-completion → Check fix side effects
↓
test-driven-development → Add regression prevention tests
↓
careful / guard → Final safe-mode review
↓
[Commit + PR]Step 1 — Understand Symptoms and Form Hypotheses (investigate)
When a bug is discovered, don't immediately fix the code. First, use the investigate skill to structure the symptoms and form hypotheses.
claude
> /skill investigate
> Bug symptom:
> "Clicking the notice upload button usually succeeds, but occasionally a 500 error occurs.
> No pattern is visible for when it happens.
> Last error log: Internal Server Error - Supabase: connection timeout"
>
> Related files:
> - app/api/notices/route.ts
> - lib/supabase/server.tsInvestigation report generated by the investigate skill:
## Bug Investigation Report
### Symptom Summary
- Frequency: Intermittent (approximately 10% of the time)
- Error type: HTTP 500 + Supabase connection timeout
- Reproduction conditions: Currently unclear
### Hypothesis List (by priority)
1. **Supabase connection pool exhaustion** (High)
- Evidence: Connection timeouts occur when the pool is exhausted
- Verification method: Monitor Active Connections in Supabase dashboard
2. **Serverless cold start** (Medium)
- Evidence: Vercel Edge/Serverless experiences cold starts when inactive
- Verification method: Compare consecutive requests vs requests after long inactivity
3. **File size exceeded** (Low)
- Evidence: Timeouts can occur when attaching large files
- Verification method: Log request body size
### Next Steps
→ Verify hypotheses in order starting with hypothesis 1 using systematic-debuggingStep 2 — Verify Hypotheses (systematic-debugging)
Verify the hypotheses formed in investigate one by one using the systematic-debugging skill.
> /skill systematic-debugging
> Verify hypothesis 1 (connection pool exhaustion) from the investigate report.
> Check how the Supabase client is created in the current code
> and find if there is a connection leak.Root cause found by the systematic-debugging skill:
// Problem code: lib/supabase/server.ts
// Creating a new client per request → connection pool exhaustion
// Before fix (problem)
export function createServerClient() {
return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
// New instance created per request → connections accumulate
}
// After fix (solution)
import { cache } from 'react';
export const createServerClient = cache(() =>
createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
);
// Reuse instance within request scope using React cache()Debug log addition pattern:
> Add temporary debug logs using systematic-debugging.
> I want to record the start/end time of Supabase queries to identify which query is timing out.Step 3 — Verify Before Fix (verification-before-completion)
Before writing the fix, use the verification-before-completion skill to check for side effects of the fix plan.
> /skill verification-before-completion
> Before applying the fix of wrapping createServerClient with cache(),
> verify the following:
> - Will existing tests break?
> - Do other API routes have the same problem?
> - Is mocking difficult in test environments when using cache()?Step 4 — Regression Prevention Tests (test-driven-development)
After fixing the bug, add tests to prevent the same bug from occurring again.
> /skill test-driven-development
> Write tests for the Supabase connection reuse logic just fixed.
> Test scenarios:
> 1. Calling createServerClient twice within the same request returns the same instance
> 2. API route responds successfully without 500 errors
> 3. Sending 10 concurrent requests without connection errors
> Framework: Vitest + testing-libraryStep 5 — Safe Mode Check (careful + guard)
After fixing, do a final review with the careful and guard skills.
> /skill careful
> Review the fixed code carefully.
> Find edge cases that could cause problems in the production environment.> /skill guard
> Check if this fix affects other features.
> Track all files that import lib/supabase/server.ts.Practical Scenario
Situation: Debugging the bug "notice uploads sometimes fail" in the Student Club Notice Board
Step 1: Bug Report and Investigation
claude
> /skill investigate
> Team member report:
> "Clicking the notice upload button usually works, but sometimes a red error appears and disappears.
> It happened twice during yesterday's demo, which was embarrassing. Can't reproduce it."
>
> Server error log (Vercel Functions dashboard):
> [ERROR] 2026-04-12T03:22:14Z - POST /api/notices
> Error: connect ETIMEDOUT 54.230.x.x:5432
> at TCPConnectWrap.afterConnectinvestigate result: Hypothesis priority — Connection pool exhaustion > Cold start > File size
Step 2: Root Cause Investigation
> /skill systematic-debugging
> Confirmed in the error log that ETIMEDOUT is on Supabase PostgreSQL port (5432).
> Investigate in the following order:
> 1. Check all client creation code in the lib/supabase/ folder
> 2. Check Supabase client usage patterns in API routes
> 3. Find points where connections are not closed and leakDiscovered problematic code:
// app/api/notices/route.ts — problem location found
import { createClient } from '@supabase/supabase-js';
// Bad pattern: creating client at module level
// Causes stale connection issues when Vercel Serverless workers are reused
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
export async function POST(request: Request) {
// use supabase...
}Step 3: Apply Fix
> Write fix code based on systematic-debugging results.
> Follow the recommended pattern from Next.js 15 App Router + Supabase SSR package.Fixed code:
// lib/supabase/server.ts
import { createServerClient as createSupabaseServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
import type { Database } from '@/types/supabase';
// Correct pattern: create new client with cookies() per request
export function createServerClient() {
const cookieStore = cookies();
return createSupabaseServerClient<Database>(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
);
},
},
}
);
}// app/api/notices/route.ts — after fix
import { createServerClient } from '@/lib/supabase/server';
export async function POST(request: Request) {
// Create client inside function → request scope isolation
const supabase = createServerClient();
const body = await request.json();
const { data, error } = await supabase
.from('notices')
.insert(body)
.select()
.single();
if (error) {
console.error('Notice creation failed:', error.message);
return Response.json({ error: 'Failed to register notice' }, { status: 500 });
}
return Response.json(data, { status: 201 });
}Step 4: Write Regression Prevention Tests
> /skill test-driven-development
> Write integration tests for POST /api/notices.
> Include the following cases:
> - Normal notice creation → 201 response
> - Sending empty title → 400 response
> - Access without authentication → 401 response
> - On Supabase error → 500 + error message returnedGenerated test code:
// __tests__/api/notices.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { POST } from '@/app/api/notices/route';
// Supabase mock
vi.mock('@/lib/supabase/server', () => ({
createServerClient: vi.fn(),
}));
describe('POST /api/notices', () => {
let mockSupabase: ReturnType<typeof vi.fn>;
beforeEach(() => {
mockSupabase = {
from: vi.fn().mockReturnThis(),
insert: vi.fn().mockReturnThis(),
select: vi.fn().mockReturnThis(),
single: vi.fn(),
};
(createServerClient as ReturnType<typeof vi.fn>).mockReturnValue(mockSupabase);
});
it('returns 201 on normal notice creation', async () => {
mockSupabase.single.mockResolvedValue({
data: { id: '1', title: 'Test Notice', content: 'Content' },
error: null,
});
const request = new Request('http://localhost/api/notices', {
method: 'POST',
body: JSON.stringify({ title: 'Test Notice', content: 'Content' }),
});
const response = await POST(request);
expect(response.status).toBe(201);
});
it('returns 500 on Supabase error', async () => {
mockSupabase.single.mockResolvedValue({
data: null,
error: { message: 'connection timeout' },
});
const request = new Request('http://localhost/api/notices', {
method: 'POST',
body: JSON.stringify({ title: 'Notice', content: 'Content' }),
});
const response = await POST(request);
expect(response.status).toBe(500);
const body = await response.json();
expect(body.error).toBe('Failed to register notice');
});
});Step 5: Final Safety Check
> /skill guard
> Check whether this fix affects the following files:
> - app/notices/page.tsx (notice list SSR)
> - app/notices/[id]/page.tsx (notice detail)
> - app/api/notices/[id]/route.ts (single read/update/delete)Recommended Skill Combinations
| Situation | Skill | Role |
|---|---|---|
| Understand bug symptoms | investigate | Form hypotheses and create investigation plan |
| Find root cause | systematic-debugging | Track cause at code level |
| Check fix side effects | verification-before-completion | Review edge cases and side effects |
| Prevent regression | test-driven-development | Tests to prevent bug recurrence |
| Careful final review | careful | Re-review production edge cases |
| Understand impact scope | guard | Impact of fix on other features |
Cautions
Common Mistakes
Fixing code immediately after seeing the symptom: Fixing "by gut feeling" without investigate often leads to fixing the wrong thing. Form hypotheses first and approach in hypothesis verification order.
Modifying multiple places simultaneously without verifying hypotheses: Changing multiple places at once makes it impossible to know which fix was effective. Verify one at a time, like systematic-debugging.
Skipping tests after fixing the bug: Skipping tests thinking "this time it's definitely fixed" leads to the same bug reappearing during refactoring 3 weeks later. Always add tests with the test-driven-development skill.
Only leaving console logs without error handling: If the function ends with only
console.error(error)and noreturn Response.json({ error: ... }, { status: 500 }), the client receives a 200 OK with empty response.Ignoring intermittent bugs as "can't reproduce": The investigate skill is most useful for bugs that can't be reproduced. Find patterns in server logs, error monitoring (Sentry), and the Supabase dashboard.
Tips
- Paste error logs as-is: When Claude Code receives a stack trace as-is, it quickly tracks the error location. Don't summarize the error message — input the whole thing.
- Common causes of intermittent Supabase errors: The four major causes are connection pool exhaustion, missing RLS policies, queries on columns without indexes, and serverless cold starts.
- Remove debug logs before PR:
console.logadded during systematic-debugging must be removed after fix completion or replaced with a structured logger (pino,winston).
| Field | Value |
|---|---|
| Source URL | https://docs.anthropic.com/en/docs/claude-code |
| License | CC BY 4.0 |
| Explanation Date | 2026-04-12 |
| Author | Claude-Code-Study Project |