디버깅 자동화: 버그를 체계적으로 찾고 고치는 Claude Code 워크플로우
한 줄 요약
"가끔 실패하는 버그"부터 "재현이 안 되는 에러"까지, investigate + systematic-debugging + test-driven-development 스킬을 조합해 근본 원인을 찾고 회귀를 방지한다.
대상 독자
- 에러 메시지를 구글에 붙여넣고 Stack Overflow 답변을 그냥 따라 쳐본 경험이 있는 학생
- 버그를 고쳤는데 다음 날 다시 발생하는 "두더지 잡기" 상황에 지친 팀원
- Supabase / Next.js 조합에서 발생하는 간헐적 에러를 디버깅해야 하는 개발자
- 디버깅 과정을 문서화해서 팀원과 공유해야 하는 팀장
핵심 워크플로우
전체 디버깅 사이클
[버그 발견]
↓
investigate → 현상 파악 + 가설 수립
↓
systematic-debugging → 가설 검증 + 루트 코즈 확정
↓
[수정 코드 작성]
↓
verification-before-completion → 수정 부작용 확인
↓
test-driven-development → 회귀 방지 테스트 추가
↓
careful / guard → 안전 모드 재확인
↓
[커밋 + PR]단계 1 — 현상 파악 및 가설 수립 (investigate)
버그를 발견하면 바로 코드를 고치지 않는다. 먼저 investigate 스킬로 현상을 구조화하고 가설을 세운다.
claude
> /skill investigate
> 버그 현상:
> "공지 업로드 버튼을 누르면 대부분 성공하는데, 가끔 500 에러가 발생한다.
> 어떤 경우에 발생하는지 패턴이 보이지 않는다.
> 마지막으로 발생한 에러 로그: Internal Server Error - Supabase: connection timeout"
>
> 관련 파일:
> - app/api/notices/route.ts
> - lib/supabase/server.tsinvestigate 스킬이 생성하는 조사 보고서:
## 버그 조사 보고서
### 현상 요약
- 발생 빈도: 간헐적 (약 10% 확률)
- 에러 타입: HTTP 500 + Supabase connection timeout
- 재현 조건: 현재 불명확
### 가설 목록 (우선순위순)
1. **Supabase 커넥션 풀 고갈** (높음)
- 근거: connection timeout은 풀이 고갈될 때 발생
- 검증 방법: Supabase 대시보드 Active Connections 모니터링
2. **서버리스 콜드 스타트** (중간)
- 근거: Vercel Edge/Serverless는 비활성 시 콜드 스타트 발생
- 검증 방법: 연속 요청과 장시간 비활성 후 요청 비교
3. **파일 크기 초과** (낮음)
- 근거: 대용량 파일 첨부 시 타임아웃 발생 가능
- 검증 방법: 요청 본문 크기 로깅
### 다음 단계
→ systematic-debugging으로 가설 1부터 순서대로 검증단계 2 — 가설 검증 (systematic-debugging)
investigate에서 세운 가설을 systematic-debugging 스킬로 하나씩 검증한다.
> /skill systematic-debugging
> investigate 보고서의 가설 1(커넥션 풀 고갈)을 검증해줘.
> 현재 코드에서 Supabase 클라이언트가 어떻게 생성되는지 확인하고
> 커넥션 누수가 있는지 찾아줘.systematic-debugging 스킬이 발견한 루트 코즈:
// 문제 코드: lib/supabase/server.ts
// 요청마다 새 클라이언트를 생성 → 커넥션 풀 고갈
// 수정 전 (문제)
export function createServerClient() {
return createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
// 각 요청마다 새 인스턴스 생성 → 커넥션이 쌓임
}
// 수정 후 (해결)
import { cache } from 'react';
export const createServerClient = cache(() =>
createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
);
// React cache()로 요청 스코프 내에서 인스턴스 재사용디버깅 로그 추가 패턴:
> systematic-debugging으로 임시 디버그 로그를 추가해줘.
> Supabase 쿼리 시작/종료 시간을 기록해서 어느 쿼리가 타임아웃인지 파악하고 싶어.단계 3 — 수정 전 검증 (verification-before-completion)
수정 코드를 작성하기 전에 verification-before-completion 스킬로 수정 계획의 부작용을 확인한다.
> /skill verification-before-completion
> createServerClient를 cache()로 감싸는 수정을 적용하기 전에
> 다음 사항을 확인해줘:
> - 기존 테스트가 깨지지 않는가
> - 다른 API 라우트에도 같은 문제가 있는가
> - cache() 사용 시 테스트 환경에서 모킹이 어렵지 않은가단계 4 — 회귀 방지 테스트 (test-driven-development)
버그를 수정한 후 같은 버그가 다시 발생하지 않도록 테스트를 추가한다.
> /skill test-driven-development
> 방금 수정한 Supabase 커넥션 재사용 로직에 대한 테스트를 작성해줘.
> 테스트 시나리오:
> 1. 동일 요청 내에서 createServerClient를 두 번 호출하면 같은 인스턴스 반환
> 2. API 라우트가 500 에러 없이 성공적으로 응답
> 3. 동시 요청 10개를 보내도 커넥션 에러 없음
> 프레임워크: Vitest + testing-library단계 5 — 안전 모드 확인 (careful + guard)
수정이 완료되면 careful과 guard 스킬로 최종 점검한다.
> /skill careful
> 수정된 코드를 신중하게 검토해줘.
> 특히 production 환경에서 문제가 될 수 있는 엣지 케이스를 찾아줘.> /skill guard
> 이 수정이 다른 기능에 영향을 주는지 확인해줘.
> lib/supabase/server.ts를 import하는 모든 파일을 추적해줘.실전 시나리오
상황: 동아리 공지 게시판에서 "공지 업로드가 가끔 실패한다"는 버그를 디버깅
1단계: 버그 신고 접수 및 조사
claude
> /skill investigate
> 팀원 신고:
> "공지 업로드 버튼 누르면 보통 되는데 가끔 빨간 에러 뜨고 사라짐.
> 어제 데모 중에 두 번 발생해서 창피했음. 재현이 안 됨."
>
> 서버 에러 로그 (Vercel Functions 대시보드):
> [ERROR] 2026-04-12T03:22:14Z - POST /api/notices
> Error: connect ETIMEDOUT 54.230.x.x:5432
> at TCPConnectWrap.afterConnectinvestigate 결과: 가설 우선순위 — 커넥션 풀 고갈 > 콜드 스타트 > 파일 크기
2단계: 루트 코즈 탐색
> /skill systematic-debugging
> 에러 로그에서 ETIMEDOUT이 Supabase PostgreSQL 포트(5432)라는 걸 확인했어.
> 다음 순서로 조사해줘:
> 1. lib/supabase/ 폴더의 모든 클라이언트 생성 코드 확인
> 2. API 라우트에서 Supabase 클라이언트 사용 패턴 확인
> 3. 커넥션이 닫히지 않고 누수되는 지점 찾기발견된 문제 코드:
// app/api/notices/route.ts — 문제 지점 발견
import { createClient } from '@supabase/supabase-js';
// 나쁜 패턴: 모듈 레벨에서 클라이언트 생성
// Vercel Serverless에서는 워커 재사용 시 오래된 커넥션 문제 발생
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
);
export async function POST(request: Request) {
// supabase 사용...
}3단계: 수정 적용
> systematic-debugging 결과를 기반으로 수정 코드를 작성해줘.
> Next.js 15 App Router + Supabase SSR 패키지의 권장 패턴을 따라줘.수정된 코드:
// lib/supabase/server.ts
import { createServerClient as createSupabaseServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
import type { Database } from '@/types/supabase';
// 올바른 패턴: 요청마다 새 클라이언트를 cookies()와 함께 생성
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 — 수정 후
import { createServerClient } from '@/lib/supabase/server';
export async function POST(request: Request) {
// 함수 내부에서 클라이언트 생성 → 요청 스코프 격리
const supabase = createServerClient();
const body = await request.json();
const { data, error } = await supabase
.from('notices')
.insert(body)
.select()
.single();
if (error) {
console.error('공지 생성 실패:', error.message);
return Response.json({ error: '공지 등록에 실패했습니다' }, { status: 500 });
}
return Response.json(data, { status: 201 });
}4단계: 회귀 방지 테스트 작성
> /skill test-driven-development
> POST /api/notices 에 대한 통합 테스트를 작성해줘.
> 다음 케이스를 포함해:
> - 정상 공지 생성 → 201 응답
> - 빈 title 전송 → 400 응답
> - 인증 없이 접근 → 401 응답
> - Supabase 에러 시 → 500 + 에러 메시지 반환생성된 테스트 코드:
// __tests__/api/notices.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { POST } from '@/app/api/notices/route';
// Supabase 모킹
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('정상 공지 생성 시 201 반환', async () => {
mockSupabase.single.mockResolvedValue({
data: { id: '1', title: '테스트 공지', content: '내용' },
error: null,
});
const request = new Request('http://localhost/api/notices', {
method: 'POST',
body: JSON.stringify({ title: '테스트 공지', content: '내용' }),
});
const response = await POST(request);
expect(response.status).toBe(201);
});
it('Supabase 에러 시 500 반환', async () => {
mockSupabase.single.mockResolvedValue({
data: null,
error: { message: 'connection timeout' },
});
const request = new Request('http://localhost/api/notices', {
method: 'POST',
body: JSON.stringify({ title: '공지', content: '내용' }),
});
const response = await POST(request);
expect(response.status).toBe(500);
const body = await response.json();
expect(body.error).toBe('공지 등록에 실패했습니다');
});
});5단계: 최종 안전 확인
> /skill guard
> 이번 수정 사항이 다음 파일들에 영향을 주는지 확인해줘:
> - app/notices/page.tsx (공지 목록 SSR)
> - app/notices/[id]/page.tsx (공지 상세)
> - app/api/notices/[id]/route.ts (단건 조회/수정/삭제)추천 스킬 조합
| 상황 | 스킬 | 역할 |
|---|---|---|
| 버그 현상 파악 | investigate | 가설 수립·조사 계획 생성 |
| 루트 코즈 탐색 | systematic-debugging | 코드 레벨 원인 추적 |
| 수정 부작용 확인 | verification-before-completion | 엣지 케이스·사이드 이펙트 점검 |
| 회귀 방지 | test-driven-development | 버그 재발 방지 테스트 |
| 신중한 최종 확인 | careful | 프로덕션 엣지 케이스 재점검 |
| 영향 범위 파악 | guard | 수정이 다른 기능에 미치는 영향 |
주의사항
자주 하는 실수
현상을 보고 바로 코드 수정: investigate 없이 "느낌으로" 수정하면 엉뚱한 곳을 고치는 경우가 많다. 가설을 먼저 세우고 가설 검증 순서대로 접근한다.
가설 검증 없이 여러 곳 동시에 수정: 한 번에 여러 곳을 바꾸면 어떤 수정이 효과가 있었는지 알 수 없다. systematic-debugging처럼 하나씩 검증한다.
버그 수정 후 테스트 작성 생략: "이번에는 확실히 고쳤으니까" 하고 테스트를 건너뛰면 3주 후 리팩토링에서 같은 버그가 재발한다. test-driven-development 스킬로 반드시 테스트를 추가한다.
콘솔 로그만 남기고 에러 처리 안 함:
console.error(error)만 하고return Response.json({ error: ... }, { status: 500 })없이 함수가 끝나면 클라이언트는 200 OK에 빈 응답을 받는다.간헐적 버그를 "재현이 안 된다"고 무시: 재현이 안 되는 버그일수록 investigate 스킬이 유용하다. 서버 로그, 에러 모니터링(Sentry), Supabase 대시보드에서 패턴을 찾는다.
팁
- 에러 로그를 그대로 붙여넣기: Claude Code는 스택 트레이스를 그대로 입력받으면 에러 발생 위치를 빠르게 추적한다. 에러 메시지를 요약하지 말고 전체를 입력한다.
- Supabase 간헐적 에러의 공통 원인: 커넥션 풀 고갈, RLS 정책 미적용, 인덱스 없는 컬럼 조회, 서버리스 콜드 스타트가 4대 원인이다.
- 디버깅 로그는 PR 전에 제거: systematic-debugging 과정에서 추가한
console.log는 수정 완료 후 반드시 제거하거나 구조화된 로거(pino,winston)로 교체한다.
| 항목 | 내용 |
|---|---|
| 원본 URL | https://docs.anthropic.com/en/docs/claude-code |
| 라이선스 | CC BY 4.0 |
| 해설 작성일 | 2026-04-12 |
| 작성자 | Claude-Code-Study 프로젝트 |