|

클로드 오류 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이 나오지 않았다.

오진 사슬 — 빈 응답을 파싱 오류로 읽으면

  1. 로그에 "JSON 파싱 실패"가 찍힌다.
  2. 프롬프트가 JSON을 잘못 유도했다고 보고 프롬프트를 고친다.
  3. 다시 돌려도 같은 실패가 난다. 이 프로젝트에서는 3회 연속 실패했다.
  4. 실제 원인은 프롬프트가 아니라 출력 예산이었다. 응답 본문(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 호출이 한 번 시간 초과되면 재시도하지 않고 결과부터 확인하게 한다. 시간 초과는 "실패했다"가 아니라 "결과를 모른다"이기 때문이다. 이미 반영된 쓰기를 다시 보내면 같은 변경이 두 번 들어간다.

클로드 오류가 나면 어떤 순서로 보나?

아래 순서대로 보면 대부분 첫 세 줄 안에서 갈린다.

  1. HTTP 상태가 200인가? 200인데 결과가 비었으면 오류가 아니라 출력 예산 문제다. stop_reason과 thinking_tokens를 본다.
  2. 400인가? 메시지에서 파라미터 이름(prefill, thinking.type, tool_choice, temperature)을 찾는다. 그대로 재시도하지 않는다.
  3. 429인가? retry-after 헤더가 있으면 속도형, 없으면 상한형이다. 상한형이면 루프를 멈춘다.
  4. 529·500인가? SDK가 이미 2회 재시도했다. 더 기다렸다 보내고, 반복되면 request_id를 남긴다.
  5. 504이거나 오래 걸리는 요청인가? 10분을 넘길 수 있는 요청은 스트리밍이나 배치 API로 옮긴다. 공식 문서는 스트리밍 중 200을 받은 뒤에도 오류가 날 수 있다고 적는다.
  6. 클로드 코드의 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로 연결 상태를 확인한다. 쓰기 작업이 시간 초과됐다면 재시도 전에 이미 반영됐는지부터 본다.

이 글이 도움이 됐다면

링크 하나면 필요한 사람에게 닿습니다.

네이버 블로그

설치 없이 바로 쓰는 무료 도구

입력값은 서버로 전송되지 않고 브라우저에서만 계산합니다.

글자수 세기 공백 포함·제외 글자수, 바이트, 원고지 매수를 한 번에 3.3% 계산기 프리랜서 원천징수 실수령액과 세전 금액 역산 실업급여 계산기 구직급여일액·소정급여일수·총 수령액을 한 번에 날짜 계산기 두 날짜 사이 일수, 며칠 뒤·며칠 전 날짜와 요일 주휴수당 계산기 주휴시간·주급 합계와 주휴 포함 시급

도구 모음 전체 보기 ›

Comments

Comments (0)

Leave a Comment

← Back to List