다음은 재시도 결함을 설명하기 위한 예시입니다. 새 문서는 잘 저장되지만 네트워크가 끊겼다 돌아온 뒤 같은 버튼을 누르면 409가 발생하고, 로그에는 고유값 제약 오류가 나타납니다. 제약을 지우면 당장 오류는 사라져도 중복 결제가 생길 수 있습니다. 익숙한 해법부터 적용하기 전에 첫 저장과 재시도의 차이를 관찰해야 합니다.

핵심 디버깅은 로그를 많이 모으는 일이 아니라 반증 가능한 가설을 한 번에 하나씩 시험하는 일입니다. 실패를 먼저 고정하고, 정상과 다른 관찰을 찾고, 그 차이만 고친 뒤 같은 실패가 돌아오지 않는지 확인합니다.

첫 프롬프트는 수정이 아니라 재현 계약입니다. 다음은 예시입니다. 저장소의 실제 테스트 이름에 맞춰 바꿉니다.

증거를 먼저 모으는 Claude Code 세션
claude --permission-mode plan

두 번째 저장에서만 409가 나는 원인을 조사하세요. 아직 수정하지 마세요.
읽기: CLAUDE.md, 저장 API, 중복 방지 키 생성부, 관련 테스트
허용: Read, Grep, 관련 테스트 1개 실행
금지: 운영 DB, 외부 결제 호출, 제약 삭제, 배포
반환: 재현 명령 / 정상·실패 차이 / 가설과 예상 관찰 / 최소 수정 후보
중단: 실패 재현이 운영 데이터나 비밀을 요구할 때

사람이 가설 하나를 고른 뒤 최소 수정만 허용합니다. 검토자는 관찰이 가설을 실제로 구분하는지 봅니다. 후속 프롬프트는 “가설 A만 검증하고 맞을 때만 관련 두 파일을 수정하세요. 다른 가설은 건드리지 마세요”처럼 씁니다.

관련 회귀, 모듈 전체 테스트, `git diff --stat`, `git diff`, `git diff --check`를 확인합니다. 수정 전 실패가 없거나 변경 차이에 계측·비밀·범위 밖 파일이 남으면 멈춥니다. 재개할 때 기각된 가설과 아직 실행하지 않은 외부 검증을 적습니다.

재현 조건을 한 문장보다 구체적으로 만듭니다. 예시 재현 계약에서 정상 조건은 새 중복 방지 키로 첫 요청을 보내 201을 받는 경우입니다. 실패 조건은 클라이언트가 응답을 받기 전에 연결이 끊긴 뒤 같은 키로 재시도해 409를 받는 경우이며, 예상 로그에는 같은 키의 두 요청과 첫 요청이 만든 행 식별자가 남습니다.

여기까지 고정하면 “DB가 느리다”나 “프런트가 두 번 눌렀다” 같은 넓은 추측을 버릴 수 있습니다. 개인 정보와 결제 값은 로그에 남기지 않고 요청 식별자, 키 해시, 상태 전이만 계측합니다.

가설은 예상 결과와 한 쌍으로 씁니다. 각 가설이 맞다면 무엇이 보여야 하는지 적으면 관찰이 맞지 않을 때 미련 없이 버릴 수 있습니다.

재시도 오류의 가설 장부
관찰: 같은 key의 첫 요청은 201, 재시도는 unique 오류 뒤 409

가설 A: 재시도 핸들러가 기존 결과를 조회하지 않는다
예상: insert 충돌 뒤 select가 실행되지 않는다

가설 B: 두 요청의 사용자 범위가 다르다
예상: 같은 key지만 owner_id가 다르다

금지 해법: unique 제약 삭제, 오류를 200으로만 치환

예상 관찰로 A와 B를 구분합니다. 예시 계측에서 두 요청의 소유자 식별자가 같고 충돌 뒤 기존 결과를 찾는 쿼리가 없다면, 인증 범위 가설 B는 기각하고 재시도 조회 누락을 지지할 수 있습니다. 수정 후보는 충돌 시 같은 소유자와 키의 기존 완료 결과를 조회해 같은 응답을 돌려주는 경로 하나입니다.

제약은 그대로 둡니다. 다른 사용자가 같은 키를 보냈을 때는 기존 결과를 돌려주지 않아야 하므로 소유자 조건도 테스트합니다.

회귀는 우연한 통과를 막아야 합니다. 예상 결과는 수정 전 두 번째 요청이 409로 실패하고, 수정 후 두 요청이 같은 결과 ID를 받으며 DB 행이 하나만 남는 것입니다. 다른 사용자, 다른 요청 본문, 처리 중 상태에서는 각각 정한 거부 또는 재시도 응답을 확인합니다.

동시 요청 테스트를 반복해 타이밍 우연을 줄이고, 임시 계측을 제거한 뒤 전체 결제 회귀를 돌립니다. 운영 데이터나 외부 결제 공급자 재현이 필요해지는 순간에는 로컬 수정을 멈추고 별도 승인을 받습니다.

증거가 끊기면 수정도 멈춥니다. 실패를 재현하지 못하거나 관찰이 두 가설을 모두 지지한다면 코드를 바꾸지 않습니다. 더 좁은 계측이나 요청 캡처가 먼저입니다. 수정 뒤 새 오류가 나타나면 원래 가설이 맞았다는 이유로 계속 덧대지 않고 변경을 되돌려 경계를 다시 잡습니다.

좋은 디버깅 결과는 그럴듯한 원인 설명이 아니라 재현 테스트, 기각된 가설, 최소 변경, 남은 미검증 영역입니다.