Claude Code 에러 진단: 로그에서 회귀 테스트까지
Claude Code로 로그 분석, 최소 재현, 회귀 테스트, 검증까지 이어지는 실무형 에러 진단 흐름을 정리합니다.
에러 진단은 터미널 마지막 줄의 빨간 메시지를 보고 감으로 고치는 일이 아닙니다. 실패를 다른 사람, 테스트 러너, Claude Code가 다시 확인할 수 있는 증거로 바꾸는 작업입니다.
먼저 세 가지 용어만 잡으면 됩니다. 로그는 명령이나 앱이 남긴 기록입니다. 스택 트레이스는 에러가 발생하기까지 호출된 함수의 경로입니다. 최소 재현은 관련 없는 화면, 데이터, 설정을 제거해도 같은 실패가 남는 작은 예제입니다.
Claude Code에 단순히 “고쳐줘”라고 하면 수정 범위가 넓어질 수 있습니다. 실패한 명령, 첫 번째 실패 줄, 제한된 원인 가설, 수정 후 반드시 통과해야 하는 검증 명령을 함께 주면 결과가 훨씬 안정됩니다.
함께 읽을 글로는 Claude Code 빌드 에러 분리 루프, Claude Code 에러 메시지 해독, Claude Code 버그 리포트 템플릿이 있습니다.
진단 흐름
flowchart LR
A["실패 명령 저장"] --> B["첫 실패 줄 찾기"]
B --> C["가설을 세 개로 제한"]
C --> D["최소 재현 만들기"]
D --> E["회귀 테스트 추가"]
E --> F["가장 작은 수정"]
F --> G["같은 명령으로 검증"]
G --> H["인수인계 메모 작성"]
이 순서를 지키면 “먼저 수정하고 나중에 증명”하는 실수를 줄일 수 있습니다. Claude Code는 많은 파일을 읽을 수 있기 때문에 지시가 넓으면 관련 없는 정리까지 제안할 수 있습니다. 진단의 목표는 변경을 늘리는 것이 아니라 원인을 좁히는 것입니다.
먼저 네 가지 증거를 모읍니다.
| 증거 | 목적 | Claude Code에 줄 예시 |
|---|---|---|
| 실패 명령 | 재현 입구를 고정 | npm run build, node --test |
| 첫 실패 줄 | 로그 끝의 소음을 피함 | TypeError, ERR_MODULE_NOT_FOUND |
| 실행 환경 | 환경 차이를 분리 | Node.js 버전, OS, CI 제공자 |
| 기대 동작 | 올바른 수정 정의 | null은 빈 배열, 404는 재시도하지 않음 |
JavaScript 에러 이름은 MDN JavaScript error reference를 기준으로 확인합니다. Node.js에서는 Node.js Errors docs를 보고, 가능하면 변하기 쉬운 메시지 문자열보다 error.code 같은 안정적인 값을 사용합니다. E2E 테스트는 Playwright debugging docs의 Inspector와 Trace Viewer를 함께 보면 로그만 볼 때보다 원인 분리가 쉽습니다.
Claude Code 실무 워크플로
실제 실패에는 아래 프롬프트를 작업 단위로 사용합니다.
claude -p "
Diagnose the following error.
1. Confirm the failing command
2. Separate the first failure from unrelated log noise
3. List up to three root-cause hypotheses
4. Create a minimal reproduction
5. Add a regression test
6. Apply the smallest fix
7. Verify with the same command and write a handoff note
Constraints:
- Do not perform unrelated refactors
- Explain any public API change before making it
- Do not call the fix complete if verification still fails
"
“최대 세 개”라는 제한이 중요합니다. 가설이 너무 많으면 검증이 흐려집니다. Cannot read properties of undefined라면 API 형태 변경, 초기값 누락, 비동기 데이터 준비 전 렌더링 정도면 충분합니다.
사용 사례1: React undefined 에러
API가 null을 반환했는데 UI가 users.map(...)을 호출하면 자주 발생합니다. 겉으로는 React 렌더링 문제처럼 보이지만 실제 원인은 데이터 형태 계약입니다.
// user-list.mjs
export function names(users) {
if (!Array.isArray(users)) {
return [];
}
return users.map((user) => user.name ?? "(no name)");
}
// user-list.test.mjs
import assert from "node:assert/strict";
import test from "node:test";
import { names } from "./user-list.mjs";
test("names returns an empty array when the API returns null", () => {
assert.deepEqual(names(null), []);
});
test("names keeps valid user names", () => {
assert.deepEqual(names([{ name: "Masa" }]), ["Masa"]);
});
node --test user-list.test.mjs
기존 구현이 return users.map(...)뿐이었다면 첫 테스트가 실패합니다. Claude Code에는 실패 테스트를 먼저 추가하고, 그 테스트를 통과시키는 최소 수정만 하라고 지시합니다.
사용 사례2: Node.js ENOENT와 import 실패
ENOENT는 보통 파일이나 디렉터리를 찾지 못했다는 뜻입니다. 문제는 config/local.json이 로컬에는 있지만 CI나 Docker 이미지에는 없을 때입니다.
확인 순서는 실패 명령, error.code, error.path, Dockerfile의 COPY, .gitignore, CI의 working directory입니다.
claude -p "
Diagnose this Node.js ENOENT failure.
Inspect error.code, error.path, current working directory, Dockerfile, and CI config.
Suggest only fixes that do not depend on a local-only file.
"
“없는 파일을 만든다”로 끝내면 운영 환경에서 다시 실패할 수 있습니다. 필수 설정이면 시작 시 명확히 실패시키고, 선택 설정이면 기본값을 두고, CI에 필요하면 체크인된 sample 파일을 복사합니다.
사용 사례3: CI에서만 실패하는 Playwright 테스트
Playwright timeout은 앱 버그, 잘못된 대기 조건, 느린 CI, 인증 만료, 네트워크 차이가 섞여 나타납니다. timeout만 늘리면 원인을 숨기기 쉽습니다.
실패 시 trace를 남깁니다.
// playwright.config.ts
import { defineConfig } from "@playwright/test";
export default defineConfig({
use: {
screenshot: "only-on-failure",
trace: "retain-on-failure",
video: "retain-on-failure",
},
});
Claude Code에는 실패한 spec, locator, 기대한 화면 상태, trace 위치, CI 명령을 줍니다. 지시는 “timeout을 늘리기 전에 UI 상태, API 응답, 인증, locator 문제를 분리해 달라”로 씁니다.
사용 사례4: TypeScript 연쇄 에러
TypeScript 에러가 20개 보여도 버그가 20개라는 뜻은 아닙니다. API 타입 하나가 바뀌어 나머지가 파생된 경우가 많습니다.
npx tsc --noEmit --pretty false 2>&1 | tee tsc.log
claude -p "
Read tsc.log and choose the first type error to fix.
Separate derived errors from the likely root cause.
After the fix, verify with npx tsc --noEmit --pretty false.
"
한 번에 모두 고치지 않습니다. 근본 타입을 고치고 같은 명령을 다시 실행한 뒤 남은 에러만 봅니다. 이 루프가 불필요한 as any를 줄입니다.
로그를 먼저 분류하는 작은 도구
아래 스크립트는 그대로 실행할 수 있습니다. 깊게 읽기 전에 로그가 어느 계열인지 대략 나눕니다.
// triage-log.mjs
import fs from "node:fs";
const sample = `
TypeError: Cannot read properties of undefined (reading 'map')
at ProductList (src/ProductList.tsx:42:18)
`;
const input = process.argv[2]
? fs.readFileSync(process.argv[2], "utf8")
: sample;
const rules = [
[/ERR_MODULE_NOT_FOUND|Cannot find module/i, "dependency or import path"],
[/ENOENT/i, "file path or working directory"],
[/TypeError:.*undefined|Cannot read properties/i, "data shape or initial value"],
[/Timeout.*expect|locator/i, "E2E wait condition or screen state"],
[/TS\d{4}/, "TypeScript type error"],
];
const matches = rules
.filter(([pattern]) => pattern.test(input))
.map(([, label]) => label);
console.log(matches.length ? matches.join("\n") : "Unclassified: inspect first failure");
node triage-log.mjs
node triage-log.mjs tsc.log
이 도구는 완전한 진단기가 아닙니다. Claude Code와 대화하기 전에 의존성, 파일 경로, 데이터 형태, E2E 대기, TypeScript 오류를 거칠게 나누는 용도입니다.
재현 가능한 버그 리포트 템플릿
조사를 요청하기 전에 문제를 아래 형식으로 정리합니다. GitHub Issues를 쓴다면 공식 GitHub issue forms syntax로 같은 항목을 폼으로 만들 수 있습니다.
## Summary
Describe the broken behavior in one sentence.
## Failed command
`npm run build`
## Expected result
The build succeeds and creates `dist/`.
## Actual result
The command fails with `TypeError: Cannot read properties of undefined`.
## First failing line
`src/components/ProductList.tsx:42:18`
## Reproduction steps
1. `npm ci`
2. `npm run build`
## Environment
- Node.js: 22.x
- OS: Windows 11 / GitHub Actions ubuntu-latest
- Branch: feature/product-list
## Already tried
- Regenerated lockfile
- Checked API response fixture
## Verification after fix
- `node --test`
- `npm run build`
핵심은 “문제가 있다”가 아니라 “이 절차로 같은 실패가 재현된다”입니다.
자주 하는 실수
첫째, 로그 마지막 줄만 붙이는 것입니다. 마지막 줄은 프로세스가 멈춘 위치일 뿐, 시작 원인은 아닐 수 있습니다.
둘째, 에러 메시지 문자열에 의존하는 것입니다. Node.js에서는 가능하면 error.code나 name을 봅니다.
셋째, 최소 재현을 만들지 않는 것입니다. 큰 화면 안에서 고치면 원인을 고쳤는지 타이밍만 바뀐 건지 알기 어렵습니다.
넷째, 회귀 테스트 없이 수정하는 것입니다. 테스트가 없으면 다음 리팩터링에서 같은 버그가 돌아옵니다.
다섯째, Claude Code에 너무 넓은 범위를 주는 것입니다. Claude Code 리뷰 워크플로 체크리스트처럼 리뷰 경계를 정해 두는 편이 안전합니다.
인수인계 메모 작성
검증 명령이 통과하면 짧은 메모를 남깁니다.
## Diagnosis note
- Failure: `npm run build`
- Cause: `users.map` was called when the API returned null
- Fix: Normalize non-array input to an empty array in `names()`
- Regression test: `node --test user-list.test.mjs`
- Verification: `npm run build` passed
- Remaining risk: Confirm separately whether API null is intentional
claude -p "
Write a Markdown diagnosis note for this fix.
Include cause, changed files, regression test, verification command,
and remaining risk. Keep it short enough for a reviewer to read in five minutes.
"
템플릿과 상담
개인 프로젝트라면 이 글의 명령과 템플릿을 복사해도 충분합니다. 팀에서는 공유 가능한 로그, Claude Code에 넣으면 안 되는 비밀값, 최소 재현 저장 위치, CI artifact, 리뷰 증거 기준을 정해야 합니다.
ClaudeCodeLab은 Claude Code 제품과 템플릿 및 Claude Code 교육과 상담을 제공합니다. 버그 리포트 폼, CI triage 프롬프트, 회귀 테스트 패턴, 인수인계 메모를 표준화하면 매번 임시 대응하는 시간을 줄일 수 있습니다.
Masa가 ClaudeCodeLab 유지보수에 이 흐름을 적용했을 때 가장 효과가 컸던 습관은 첫 실패 줄 보존, 최소 재현 작성, 같은 명령 재검증이었습니다. Claude Code의 제안이 단순한 추측이 아니라 증거, 가설, 테스트, 검증 결과가 붙은 변경으로 바뀌었습니다.
무료 PDF: Claude Code 치트시트
이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.
개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.
작성자 소개
Masa
Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.
관련 글
Claude Code 권한 세이프티 래더: 통제력을 잃지 않고 allow 넓히기
read-only에서 제한 편집, 검증 명령, deploy 확인까지 권한을 단계적으로 넓히는 방법.
Claude Code Small PR Proof Pack: 작은 PR을 리뷰 가능한 상태로 만드는 증거 세트
Claude Code의 작은 PR에 diff, 검증, 공개 URL, CTA 경로, rollback을 붙이는 실무 체크리스트.
Claude Code 커밋 전 리뷰 게이트: diff, 테스트, 공개 URL, CTA 확인
Claude Code 작업을 커밋하기 전에 diff 범위, build, 공개 URL, Gumroad 링크, 상담 CTA, 테스트 누락과 무관한 파일을 확인하는 방법입니다.