클로드 오류 400·429·529 읽는 법 — 빈 응답은 오류가 아니라 사고가 토큰을 다 쓴 것
3줄 요약
- 클로드 API 오류는 늘 같은 JSON 모양(type·message·request_id)으로 온다. 400은 내 요청 문제, 429는 한도 문제, 529는 서버 전체 과부하로 읽으면 첫 판단이 빨라진다.
- 429는 한 가지가 아니다. 속도 한도, 등급별 월 지출 상한, 클로드 코드 워크스페이스 지출 한도가 모두 429로 오고, 월 지출 상한 429는 retry-after 없이 계속 실패한다.
- 가장 헷갈리는 것은 오류가 아닌 실패다. 응답은 200인데 텍스트가 비어 있으면, 사고(thinking) 토큰이 max_tokens를 다 쓴 것이다. 이 프로젝트 실측에서 출력 5,369토큰 중 4,474토큰이 사고였다.
| 질문 | 답 |
|---|---|
| 400은 무엇인가 | invalid_request_error. 요청 형식·내용 문제. 재시도해도 안 풀린다 |
| 429는 무엇인가 | rate_limit_error. 속도 한도·월 지출 상한·클로드 코드 워크스페이스 한도 중 하나 |
| 529는 무엇인가 | overloaded_error. 전체 사용자 트래픽이 몰린 일시 과부하 |
| 응답이 비어 있으면 | 오류가 아니다. stop_reason이 max_tokens인지, 사고 토큰이 얼마였는지 본다 |
| 지원팀에 무엇을 주나 | 응답의 request_id(헤더 request-id) |
이 글대로 하면 무엇이 달라지는가: 오류 메시지를 보고 "재시도할 것인가, 요청을 고칠 것인가, 기다릴 것인가"를 첫 줄에서 가를 수 있게 되고, 오류 로그가 비어 있는데 결과도 비어 있는 실패를 따로 잡아낼 수 있다.
클로드 오류는 어떤 모양으로 오나?
클로드 API 오류란 HTTP 상태 코드와 함께 돌아오는 JSON 객체다. 공식 오류 문서(platform.claude.com/docs/en/api/errors)는 오류가 항상 최상위 error 객체에 type과 message를 담고, 추적용 request_id를 함께 준다고 적는다.
{
"type": "error",
"error": {
"type": "rate_limit_error",
"message": "..."
},
"request_id": "req_..."
}
판정에 쓰는 것은 message가 아니라 error.type이다. 공식 문서는 SDK가 상태 코드별로 타입이 정해진 예외를 던지므로 메시지 문자열을 비교하지 말고 예외 클래스를 잡으라고 권한다. 메시지 문구는 바뀔 수 있다는 단서도 붙어 있다.
400·429·529는 각각 무엇을 뜻하나?
400은 내 요청을 고쳐야 하고, 429는 한도를 확인해야 하고, 529는 기다려야 한다. 공식 문서 기준 전체 표는 다음과 같다.
| 코드 | type | 뜻 | 먼저 할 일 |
|---|---|---|---|
| 400 | invalid_request_error |
요청 형식·내용 문제. 내가 정한 조직·워크스페이스 지출 한도에 닿아도 400 | 메시지를 읽고 요청을 고친다. 그대로 재시도 금지 |
| 401 | authentication_error |
키가 잘못됐거나 폐기·만료 | 키 교체 |
| 402 | billing_error |
결제 정보 문제 | Console에서 결제 확인 |
| 403 | permission_error |
키에 해당 자원 권한이 없음 | 워크스페이스 권한 확인 |
| 413 | request_too_large |
요청 크기 초과(Messages API 32MB) | 입력을 줄이거나 Files API로 |
| 429 | rate_limit_error |
속도 한도·월 지출 상한·클로드 코드 워크스페이스 한도 | retry-after 헤더가 있는지 본다 |
| 500 | api_error |
내부 오류 | 지수 백오프 재시도, 계속되면 request_id로 문의 |
| 504 | timeout_error |
처리 중 시간 초과 | 스트리밍이나 배치 API로 |
| 529 | overloaded_error |
전체 사용자 트래픽이 몰린 일시 과부하 | 기다렸다 재시도 |
공식 SDK는 연결 오류·429·5xx 같은 일시 실패를 기본 2회, 지수 백오프로 자동 재시도하고 retry-after 헤더가 있으면 따른다. 그래서 내 코드에 예외가 올라왔다면 이미 SDK가 두 번 더 해 본 뒤라는 뜻이다.
429를 하나로 읽으면 안 되는 이유
공식 문서는 429의 원인을 셋으로 적는다. 속도 한도(rate limit)에 닿은 경우, 사용 등급(tier)의 월 지출 상한에 닿은 경우, 클로드 코드 워크스페이스의 지출 한도에 닿은 경우다. 이 중 등급 월 지출 상한 429는 retry-after 헤더가 없고, 접근이 다시 열릴 때까지 계속 실패한다. 이걸 속도 한도로 오해해 백오프 재시도를 걸면 루프가 하루 종일 같은 429를 받는다.
판별식은 짧다.
import anthropic
def classify_429(err: anthropic.RateLimitError) -> str:
ra = err.response.headers.get("retry-after")
if ra is None:
return "상한형: 재시도 중단, 지출 상한·워크스페이스 한도 확인"
return f"속도형: {ra}초 뒤 재시도"
같은 맥락에서 529도 구별한다. 공식 문서는 529가 전체 사용자 트래픽이 몰릴 때 나고, 드물게 조직의 사용량이 급격히 늘면 가속 한도 때문에 429가 날 수 있다고 적는다. 대책은 트래픽을 점진적으로 올리고 사용 패턴을 고르게 유지하는 것이다.
400인데 요청 형식이 멀쩡해 보일 때는 무엇을 보나?
모델이 바뀌면서 받지 않게 된 파라미터를 본다. 공식 문서에 적힌 대표 400 메시지는 다음과 같다.
| 메시지에 보이는 말 | 원인 | 고치는 법 |
|---|---|---|
does not support assistant message prefill |
4.6 이후 모델은 마지막 assistant 메시지 미리 채우기를 받지 않는다 | 구조화 출력·시스템 프롬프트로 대체 |
"thinking.type.enabled" is not supported |
4.7 이후 모델은 고정 예산(budget_tokens) 사고를 제거했다 |
thinking: {type: "adaptive"} + effort |
thinking blocks ... cannot be modified |
직전 assistant 턴의 사고 블록을 편집·누락해 되돌려 보냈다 | 받은 그대로 되돌려 보낸다 |
tool_choice: type "tool" and "any" are not supported |
일부 최신 모델은 강제 도구 호출을 받지 않는다 | auto + strict tool use |
이 프로젝트에서 실제로 난 것도 이 계열이다. 이 프로젝트의 LLM 호출 모듈(scripts/claude_llm.py)에는 "temperature 는 넣지 않는다 — 최신 모델에서 deprecated 라 400 이 난다(실측)"는 주석이 남아 있다. 옛 예제 코드를 그대로 옮겨 temperature를 넣었다가 400을 받은 기록이다. 공식 오류 문서에는 temperature 관련 400 문구가 따로 실려 있지 않으므로, 이 줄은 이 프로젝트 환경에서 관측한 사실로만 적어 둔다.
오류 없이 빈 응답이 오는 이유는 무엇인가?
사고(thinking) 토큰이 max_tokens를 먼저 다 썼기 때문이다. 공식 사고 문제 해결 문서(thinking-troubleshooting)는 이 증상을 그대로 적는다: 응답이 stop_reason: "max_tokens"로 끝나고, 텍스트 블록이 잘리거나 아예 없다. 원인은 "thinking tokens count toward max_tokens"다. HTTP는 200이고 예외도 없다. 그래서 오류 로그를 아무리 봐도 안 보인다.
이 프로젝트의 실측(2026-09-16)은 이랬다. 글 주제 계획을 JSON으로 받는 호출이 빈 문자열을 받아 "JSON을 못 찾았다"로 죽었다. 같은 요청을 원시 API로 재현해 usage를 보니 출력 5,369토큰 중 4,474토큰이 사고였다. 남은 895토큰으로는 요구한 JSON이 나오지 않았다.
오진 사슬 — 빈 응답을 파싱 오류로 읽으면
- 로그에 "JSON 파싱 실패"가 찍힌다.
- 프롬프트가 JSON을 잘못 유도했다고 보고 프롬프트를 고친다.
- 다시 돌려도 같은 실패가 난다. 이 프로젝트에서는 3회 연속 실패했다.
- 실제 원인은 프롬프트가 아니라 출력 예산이었다. 응답 본문(raw)이 빈 문자열이었다는 점이 단서였다.
여기서 구별 조건이 중요하다. 프롬프트가 JSON 대신 산문을 유도한 경우라면 raw가 비어 있지 않다. raw가 빈 문자열일 때만 "사고가 예산을 다 썼다"는 진단이 맞다.
판별 코드
def diagnose_empty(resp) -> str | None:
text = "".join(b.text for b in resp.content if b.type == "text")
if text.strip():
return None
thinking = getattr(getattr(resp.usage, "output_tokens_details", None), "thinking_tokens", None)
return (f"빈 응답: stop_reason={resp.stop_reason}, "
f"output={resp.usage.output_tokens}, thinking={thinking}")
공식 문서가 사고 비용을 보라고 지목한 필드가 usage.output_tokens_details.thinking_tokens다. 스트리밍이면 이 값은 마지막 message_delta 이벤트에만 실린다.
고치는 법 — 공식 해법과 이 프로젝트의 선택
공식 문서가 제시하는 해법은 두 가지다. max_tokens를 올려 사고와 텍스트 둘 다 들어갈 자리를 만들거나, effort를 낮춰 사고에 덜 쓰게 하는 것이다.
이 프로젝트는 세 번째 길을 택했다. 한 번에 요구하는 산출물을 줄였다. "N편 계획을 한 번에"를 "뼈대 1회 + 편당 목차 1회"로 나누자, 3회 연속 실패하던 호출이 한 번에 통과했다. 요구가 작아지면 필요한 사고도, 필요한 출력도 같이 작아진다. 어느 쪽이 맞는지는 작업마다 다르다 — 한 번에 받아야 하는 산출물이면 공식 해법을, 쪼갤 수 있는 산출물이면 분할을 쓴다.
클로드 코드에서 MCP 도구가 첫 호출만 실패할 때는?
서버가 아직 안 깨어난 것(콜드 스타트)부터 의심한다. 이 프로젝트가 쓰는 검색 콘솔 MCP 서버는 세션 첫 호출이 시간 초과로 실패하고, 가벼운 도구 하나를 먼저 불러 서버를 깨운 뒤에는 정상으로 돈다는 기록이 운영 메모에 남아 있다.
클로드 코드 MCP 문서(code.claude.com/docs/en/mcp)가 주는 조절 장치는 셋이다.
| 장치 | 무엇을 조절하나 | 예 |
|---|---|---|
MCP_TIMEOUT 환경변수 |
서버 시작 대기 시간 | MCP_TIMEOUT=10000 claude → 10초 |
.mcp.json의 서버별 timeout |
그 서버의 도구 실행 시간(밀리초) | "timeout": 600000 → 10분 |
MAX_MCP_OUTPUT_TOKENS |
도구 출력 토큰 상한 | 문서 기준 기본 25,000, 10,000을 넘으면 경고 |
서버 상태는 /mcp로 본다. 서버마다 Connected, ! Needs authentication, Failed to connect 중 하나가 표시된다.
다만 시간 초과가 쓰기 작업에서 났다면 재시도 전에 멈춘다. 이 프로젝트 규칙은 DB를 바꾸는 MCP 호출이 한 번 시간 초과되면 재시도하지 않고 결과부터 확인하게 한다. 시간 초과는 "실패했다"가 아니라 "결과를 모른다"이기 때문이다. 이미 반영된 쓰기를 다시 보내면 같은 변경이 두 번 들어간다.
클로드 오류가 나면 어떤 순서로 보나?
아래 순서대로 보면 대부분 첫 세 줄 안에서 갈린다.
- HTTP 상태가 200인가? 200인데 결과가 비었으면 오류가 아니라 출력 예산 문제다.
stop_reason과thinking_tokens를 본다. - 400인가? 메시지에서 파라미터 이름(
prefill,thinking.type,tool_choice,temperature)을 찾는다. 그대로 재시도하지 않는다. - 429인가?
retry-after헤더가 있으면 속도형, 없으면 상한형이다. 상한형이면 루프를 멈춘다. - 529·500인가? SDK가 이미 2회 재시도했다. 더 기다렸다 보내고, 반복되면
request_id를 남긴다. - 504이거나 오래 걸리는 요청인가? 10분을 넘길 수 있는 요청은 스트리밍이나 배치 API로 옮긴다. 공식 문서는 스트리밍 중 200을 받은 뒤에도 오류가 날 수 있다고 적는다.
- 클로드 코드의 MCP 도구인가?
/mcp로 상태를 보고, 첫 호출만 실패하면MCP_TIMEOUT을 늘린다.
한계 — 이 표로 안 통하는 경우
위 표는 API가 돌려준 오류를 읽는 법이다. 클로드 코드 화면에 뜨는 구독 한도 메시지("You've hit your session limit" 등)는 HTTP 오류 코드가 아니라 요금제 한도 안내라서 이 표로 판정하지 않는다. 또 오류 메시지의 세부 문구는 공식 문서가 "바뀔 수 있다"고 밝혀 두었으므로, 코드에서 문구를 문자열로 비교하는 판정은 언제든 조용히 깨질 수 있다.
자동화가 "성공"이라고 말하는데 결과물이 비어 있는 실패는 이 글의 빈 응답과 같은 계열이다. 이 프로젝트가 그런 실패를 어떻게 잡았는지는 CDP 연결이 탭 하나 때문에 멈춘 기록과 에이전트 결과를 교차 검증하는 하네스에 있다. LLM에 맡기지 말아야 할 변환 작업은 MD를 HTML로 바꿀 때 LLM을 쓰지 않는 이유에서 다뤘다.
자주 묻는 질문
Q. 클로드 529 overloaded 오류는 내 계정 문제인가?
A. 아니다. 공식 문서 기준 529는 전체 사용자 트래픽이 몰릴 때 나는 일시 과부하다. SDK가 이미 2회 자동 재시도한 뒤 올라온 것이므로 잠시 기다렸다 보낸다. 조직 사용량이 급증할 때는 드물게 가속 한도 때문에 429가 날 수 있다.
Q. 클로드 429 오류는 기다리면 풀리나?
A. 종류에 따라 다르다. retry-after 헤더가 있으면 속도 한도라 그 시간 뒤 풀린다. 헤더가 없으면 등급 월 지출 상한 같은 상한형이라 접근이 다시 열릴 때까지 계속 실패한다. 이때는 재시도를 멈추고 지출 상한을 확인한다.
Q. 응답이 비어 있는데 오류는 없다. 무엇이 문제인가?
A. 사고(thinking) 토큰이 max_tokens를 먼저 다 쓴 경우가 대표적이다. stop_reason이 max_tokens인지, usage.output_tokens_details.thinking_tokens가 출력의 대부분인지 본다. 이 프로젝트 실측에서는 출력 5,369토큰 중 4,474토큰이 사고였다.
Q. 400 오류는 재시도하면 되나?
A. 안 된다. 400은 요청 형식·내용 문제라 같은 요청은 같은 결과를 낸다. 메시지에 나온 파라미터(prefill, thinking.type, tool_choice 등)를 고쳐서 보낸다.
Q. 클로드 코드에서 MCP 도구가 처음 한 번만 시간 초과된다. 왜 그런가?
A. 서버가 아직 시작 중인 콜드 스타트일 가능성이 크다. MCP_TIMEOUT 환경변수로 시작 대기 시간을 늘리고 /mcp로 연결 상태를 확인한다. 쓰기 작업이 시간 초과됐다면 재시도 전에 이미 반영됐는지부터 본다.
새 글을 메일로 받아보기
광고 없이 새 글과 자료만 보냅니다. 언제든 해지할 수 있습니다.
설치 없이 바로 쓰는 무료 도구
입력값은 서버로 전송되지 않고 브라우저에서만 계산합니다.
Comments
Comments (0)
Leave a Comment