200 OK를 받았는데 값은 저장되지 않았다
| 매출 | 비용 | 시간 | 실패 |
|---|---|---|---|
| - | - | - | - |
무엇을 했나
위탁판매 자동화 작업은 AI 에이전트인 Claude Code에게 맡겼다. 아래 내용은 그때 남긴 기록을 기준으로 한다.
Claude Code가 네이버 커머스API로 스마트스토어 상품을 등록했다. 판매자 관리 코드(sellerManagementCode)를 originProduct 최상위에 넣었다. 응답은 200이었고 상품 등록도 완료됐다.
그런데 주문이 들어와도 어느 도매처 상품인지 역추적할 수 없었다. 판매자 관리 코드가 저장되지 않았기 때문이다. 이 값은 detailAttribute.sellerCodeInfo 아래에 넣어야 했다. brandName과 manufacturerName도 마찬가지였다. 200을 받았지만 값은 사라졌다. 지금 코드는 판매자 관리 코드를 detailAttribute.sellerCodeInfo, 브랜드를 detailAttribute.naverShoppingSearchInfo 아래에 넣어 보낸다.
정리하면 이번 요청에서는 필드를 잘못된 위치에 넣어도 400 에러가 나지 않고 200 응답이 왔으며, 해당 값은 실제 상품에 저장되지 않았다. 200은 요청이 처리됐다는 뜻이지, 내가 보낸 값이 의도대로 저장됐다는 뜻은 아니었다.
결과
이 일 이후 Claude Code는 규칙을 하나 세웠다. 쓰고 나면 반드시 되읽는다. 요청 → 200 → 끝이 아니라, 요청 → 응답 → 다시 조회 → 기대값과 비교 → 완료로 바꾼 것이다. 예를 들어 판매중지를 요청한 뒤에는 상품을 다시 조회해서 statusType이 SUSPENSION인지 확인한다. 값이 다르면 “판매중지 반영 안 됨”으로 처리한다.
이 규칙 덕분에 이후 대량 작업은 “성공했다”가 아니라 “되읽어 확인했다”로 보고할 수 있었다. 그렇게 확인한 작업은 아래와 같다.
| 작업 | 건수 |
|---|---|
| 가격 반영 | 40건 |
| 카테고리 이동 | 6건 |
| 재고 동기화 | 6건 |
같은 성격의 버그가 우리 코드에도 있었다. 파이썬 requests의 응답 객체를 if resp:로 검사하고 있었는데, Response.__bool__은 resp.ok를 돌려준다. 그래서 400 응답을 False로 처리했고, 이후 로직에서 에러 본문을 읽지 않은 채 버렸다. 로그에는 “API 응답 없음”만 남았다. 조건을 resp is not None으로 고치자 그동안 숨어 있던 진짜 에러 메시지들이 한꺼번에 드러났다.
두 문제는 생김새가 같다. 하나는 API가 성공처럼 보이게 하고 실제로는 값을 버렸고, 다른 하나는 우리 코드가 실패를 “응답 없음”으로 덮었다. 둘 다 응답만 보고는 무엇이 잘못됐는지 알 수 없었다. 둘 다 HTTP 응답만 보면 작업이 끝난 것처럼 보였다. 그래서 자동화에서 “요청 성공”을 “작업 성공”으로 취급하지 않기로 했다.
다음 행동
쓰기 작업은 200 응답으로 끝내지 않고, 다시 조회해서 값이 실제로 들어갔는지 확인한 뒤에 완료로 본다. 되읽기에서 어긋난 건수도 함께 기록한다. 등록·수정에 쓰는 주요 필드를 표본으로 골라, 요청값과 다시 조회한 값을 자동으로 비교한다.
정리
측정한 것
- 비용: 기록하지 않음
- 시간: 기록하지 않음
- 결과: 되읽어 확인한 대량 작업은 가격 반영 40건, 카테고리 이동 6건, 재고 동기화 6건
확실한 것
- 이번 요청에서 필드를 잘못된 위치에 넣었을 때 400이 아니라 200이 왔고, 값은 저장되지 않았다.
- 판매자 관리 코드(
sellerManagementCode)는detailAttribute.sellerCodeInfo아래에 넣어야 저장된다. if resp:검사는 400 응답을False로 판정한다. 그 조건으로 분기하면 에러 본문을 읽지 않고 지나가게 된다.
아직 모르는 것
- 200을 받고 값이 사라지는 필드가 이 세 개 말고 더 있는지는 아직 모른다. 주요 필드의 요청값과 재조회값을 자동 비교해서 확인한다.