Claude Code가 API Error: 400 due to tool use concurrency issues를 보여 주면 도구를 사용하는 대화 상태가 일치하지 않는 것입니다. key 교체, quota 구매, provider 변경, 전체 세션 삭제를 첫 조치로 삼지 마세요.
2026년 7월 15일 기준 Anthropic 오류 문서에는 먼저 처리할 좁은 버전 분기가 있습니다. Opus 4.7 또는 4.8을 Claude Code v2.1.156 이전 버전에서 사용 중이라면 Claude Code부터 업데이트하세요. 정상적인 tool use에서도 mismatch가 생길 수 있고 /rewind가 그 상태를 지우지 못할 수 있습니다. 다른 version/model이라면 작업 문맥을 저장하고 /rewind 또는 Esc 두 번으로 깨진 turn 이전으로 돌아간 뒤 작은 tool action 하나로 확인합니다.
API loop를 직접 운영한다면 assistant의 각 tool_use에 정확히 하나의 tool_result를 대응시키고, 전체 결과를 다음 user message 하나에 넣으며, 모든 결과를 텍스트보다 앞에 둡니다.
| 사용 환경 | 첫 안전 조치 | 성공 신호 |
|---|---|---|
| Opus 4.7/4.8 + Claude Code v2.1.156 이전 | Claude Code를 업데이트하고 작은 action 재실행 | 업데이트한 client에서 같은 action 완료 |
| 그 밖의 Claude Code CLI / IDE session | 작업을 저장하고 깨진 turn 이전으로 rewind | 새 tool turn이 400 없이 완료 |
| 직접 만든 Anthropic client | 마지막 assistant tool turn과 다음 result batch 감사 | 모든 ID 일치, validator 빈 배열, 1회 replay 성공 |
적용 가능한 update 또는 한 번의 깨끗한 rewind 뒤에도 같은 작은 action이 실패하면 복구 반복을 멈추세요. version, surface, exact error, 실패한 두 turn의 sanitized shape를 보존하고, 깨진 transcript가 아닌 사람용 작업 요약으로 새 session을 시작하거나 범위를 제한한 report를 제출합니다.
이 400이 실제로 가리키는 것
“tool use concurrency issues”라는 표현은 동시에 너무 많은 도구를 실행했다는 뜻처럼 보입니다. 하지만 이 오류에서 더 중요한 질문은 실행 개수가 아니라 “다음 요청에 실린 대화 상태가 API가 이어 갈 수 있는 형태인가”입니다. Claude Code 문서는 이 오류를 tool use 또는 thinking block mismatch와 연결하고, Claude API의 HTTP 400은 요청 형식이나 내용이 잘못된 invalid_request_error 범주입니다.
즉 모델이 작업 자체를 거절한 것이 아니라, 다음 요청의 도구 기록을 해석할 수 없다는 뜻입니다. Claude Code에서는 긴 세션, 중단된 도구 실행, 끊긴 스트림, IDE 확장의 오래된 상태, 사용자가 편집한 히스토리, 압축된 메시지가 원인이 될 수 있습니다. 직접 만든 클라이언트에서는 tool_result 누락, 여러 user message로 나뉜 결과, 결과 앞에 삽입된 텍스트, 잘못된 tool_use_id가 흔한 원인입니다.
그래서 일반 채팅은 응답하지만 파일 읽기나 편집만 실패할 수 있습니다. 텍스트 대화는 도구 결과의 짝을 필요로 하지 않습니다. 하지만 Read, Edit, Bash, Search 같은 도구 동작은 ID, 순서, role, content block type이 모두 맞아야 합니다. 이 차이를 무시하면 네트워크나 결제 문제만 오래 보게 됩니다.
Claude Code 세션을 먼저 복구하세요
첫 단계는 전체 transcript를 보존하는 것이 아니라 사람이 다시 사용할 수 있는 작업 상태를 남기는 것입니다. 현재 목표, 수정 중인 파일, 직전에 실행한 도구, 정확한 오류 문구, 시간대가 포함된 시각, CLI인지 IDE인지, 일반 채팅이 가능한지를 기록합니다. 이 정보는 새 세션이 필요해져도 안전하게 옮길 수 있습니다.
먼저 claude --version을 기록합니다. Opus 4.7/4.8과 v2.1.156 이전 조합이면 update부터 하고 알려진 예외를 여러 번 /rewind로 재검증하지 마세요. 그 외에는 VS Code나 Cursor와 CLI에서 같은 작은 tool action을 비교합니다. CLI만 되면 확장 상태나 bridge, 두 표면 모두 같은 session에서 실패하면 session history를 먼저 봅니다.
세션 자체가 깨졌다고 보이면 /rewind를 사용합니다. Claude Code에서는 Esc를 두 번 눌러 이전 대화 상태로 돌아가는 방법도 사용할 수 있습니다. 돌아온 직후에는 큰 복합 지시를 하지 마세요. 여러 파일 수정, 테스트 실행, 문서 검색을 한꺼번에 요청하지 말고 파일 하나 읽기 같은 작은 동작으로 복구 여부를 확인합니다.
그 작은 동작도 같은 exact error를 내면 새 세션을 검토합니다. 새 세션에는 작업 요약, 관련 파일, 마지막으로 신뢰할 수 있는 상태, 민감정보를 뺀 오류 증거만 가져갑니다. 손상된 도구 히스토리 전체를 붙여 넣으면 새 대화에서도 같은 400을 다시 만들 수 있습니다.
IDE, Cursor, VS Code에서는 표면을 나눠 보세요
IDE 통합에서 이 오류가 나면 “일반 대화는 되는데 도구만 실패”하는 모양이 자주 보입니다. 이것은 사용자가 실제로 여러 도구를 동시에 실행했다는 증거가 아닙니다. IDE 확장이 부분 상태를 저장했거나, CLI와 에디터 사이의 bridge가 오래된 메시지를 재전송했거나, remote environment 버전이 다르거나, 확장 쪽만 손상된 tool turn을 들고 있을 수 있습니다.
순서는 단순합니다. 먼저 표면을 기록합니다. Claude Code CLI, VS Code, Cursor, remote SSH, WSL, terminal multiplexer, wrapper 중 어디인지 적습니다. 그다음 같은 작은 도구 동작을 CLI와 IDE에서 비교합니다. 그 후 버전을 확인합니다. 재시작, 업데이트, 재설치, 특정 버전으로 되돌리기는 증거를 보존한 뒤에 해도 늦지 않습니다.
IDE만 실패하고 CLI가 작동하면 작업은 CLI로 계속 진행할 수 있습니다. CLI와 IDE가 같은 세션에서 모두 실패하면 IDE만 탓하지 말고 세션을 되돌리세요. 깨끗한 새 세션에서도 같은 작은 동작이 실패할 때 issue나 support ticket을 준비하면 됩니다.
한국어 환경에서는 “로그아웃 후 재인증”이나 “세션 재시작”이 먼저 떠오르기 쉽습니다. 인증이나 billing 문구가 실제로 보이지 않는다면, 그것은 보조 확인일 뿐입니다. 이 exact 400의 중심은 도구 기록과 message shape입니다.
직접 만든 API 클라이언트라면 messages 배열을 보세요
agent framework, IDE integration, backend tool runner를 만들고 있다면 원인은 사용자의 프롬프트보다 messages 배열에 있을 가능성이 큽니다. assistant가 한 turn에서 하나 이상의 tool_use를 냈다면, 클라이언트는 도구를 실행한 뒤 다음 user message에 결과를 돌려줘야 합니다. 실행은 병렬이어도 순차여도 되지만, 반환 형태는 엄격합니다.
지켜야 할 규칙은 세 가지입니다. 모든 tool_use.id에는 대응하는 tool_result.tool_use_id가 있어야 합니다. 같은 assistant turn에서 나온 결과는 다음 user message 하나에 모아야 합니다. 그 user message에 일반 텍스트도 들어간다면 tool_result 블록이 먼저 와야 합니다. 결과 앞에 “결과를 받았습니다” 같은 문장을 넣는 구현은 사람에게는 자연스럽지만 API에는 깨진 형태입니다.
| 클라이언트 실수 | 왜 실패하는가 | 수정 |
|---|---|---|
tool_result 하나가 빠짐 | assistant가 요청한 도구 호출이 닫히지 않음 | 각 ID에 결과를 주거나 손상된 tool turn 전체를 replay history에서 제거 |
| 결과를 여러 user message로 나눔 | 한 assistant turn의 결과 세트가 분리됨 | 다음 user message 하나에 모든 결과를 모음 |
텍스트가 tool_result 앞에 있음 | 결과 블록이 먼저 와야 함 | 모든 결과를 먼저 두고 필요한 설명을 뒤에 둠 |
| failed / skipped call의 result가 없음 | assistant call이 미해결로 남음 | matching result와 is_error: true를 반환 |
| duplicate 또는 unknown ID | 이전 assistant turn과 일대일 대응하지 않음 | duplicate를 제거하고 unknown ID를 거부 |
| history compaction이 tool block을 삭제 | 짝과 순서가 깨짐 | tool_use / tool_result 쌍을 통째로 보존하거나 통째로 제거 |
로그에 필요한 것은 shape이지 비밀이 아닙니다. role 순서, block type, tool ID, 결과 수, 결과 위치를 확인하면 충분합니다. API key, token, 개인 파일 내용, 고객 데이터, 기밀 transcript는 로그에 넣지 마세요. tool runner 가까이에 validator를 두면 잘못된 request를 API에 보내기 전에 막을 수 있습니다.
replay 전에 실패한 turn을 validator로 검사하세요
client tool_use가 들어 있는 마지막 assistant message와 바로 다음 user message를 분리합니다. 아래 TypeScript는 missing, duplicate, unknown ID, block 순서, failed/skipped result, pending server tool을 검사합니다.
hljs tstype Block = { type: string; id?: string; tool_use_id?: string; is_error?: boolean };
type Message = { role: "assistant" | "user"; content: Block[] };
export function auditToolTurn(
assistant: Message,
nextUser: Message,
options: { failedOrSkippedIds?: ReadonlySet<string>; hasPendingServerTool?: boolean } = {},
): string[] {
const problems: string[] = [];
const calls = assistant.content.filter((b) => b.type === "tool_use");
const results = nextUser.content.filter((b) => b.type === "tool_result");
const callIds = calls.flatMap((b) => b.id ? [b.id] : []);
const resultIds = results.flatMap((b) => b.tool_use_id ? [b.tool_use_id] : []);
const count = (ids: string[], id: string) => ids.filter((x) => x === id).length;
if (assistant.role !== "assistant") problems.push("Expected an assistant tool-use turn");
if (nextUser.role !== "user") problems.push("Tool results must be in the next user message");
for (const id of new Set(callIds)) {
if (count(callIds, id) > 1) problems.push(`Duplicate tool_use id: ${id}`);
if (count(resultIds, id) === 0) problems.push(`Missing tool_result: ${id}`);
if (count(resultIds, id) > 1) problems.push(`Duplicate tool_result: ${id}`);
}
for (const id of new Set(resultIds)) if (!callIds.includes(id)) problems.push(`Unexpected tool_result: ${id}`);
const firstNonResult = nextUser.content.findIndex((b) => b.type !== "tool_result");
if (firstNonResult >= 0 && nextUser.content.slice(firstNonResult + 1).some((b) => b.type === "tool_result")) {
problems.push("Every tool_result must appear before text or other user content");
}
for (const id of options.failedOrSkippedIds ?? []) {
const result = results.find((b) => b.tool_use_id === id);
if (result && result.is_error !== true) problems.push(`Failed or skipped call must use is_error: true: ${id}`);
}
if (options.hasPendingServerTool && nextUser.content.some((b) => b.type !== "tool_result")) {
problems.push("Pending server tool: next user message must contain client tool_result blocks only");
}
return problems;
}
빈 배열은 이 두 turn이 위 구조 검사를 통과했다는 뜻일 뿐, 과거 thinking blocks 보존까지 증명하지는 않습니다. 잘못된 데이터를 만든 runner나 저장 레코드를 고친 뒤 한 번만 replay하세요.
병렬 실행은 가능하지만 결과 반환은 엄격합니다
병렬 tool use가 금지된 것은 아닙니다. Claude는 한 assistant message에서 여러 tool call을 요청할 수 있습니다. 클라이언트는 이를 동시에 실행할 수도 있고, 외부 시스템이 불안정하면 순차로 실행할 수도 있습니다. API가 엄격하게 보는 것은 실행 방식이 아니라 결과가 대화에 돌아오는 방식입니다.
따라서 “앞으로 병렬로 실행하지 말라”는 조언만으로는 부족합니다. 실행을 하나씩 하더라도 serializer가 ID를 잃어버리거나, 결과를 여러 message로 나누거나, 텍스트를 앞에 두거나, 오래된 history를 잘못 압축하면 같은 오류가 납니다. 반대로 결과를 모두 모아 올바르게 반환한다면 병렬 실행도 유효합니다.
disable_parallel_tool_use는 side effect가 있는 도구, 동시에 쓰면 깨지는 외부 시스템, 안정적인 aggregator가 없는 runner처럼 실제 제한이 있을 때만 사용하세요. request 최상위가 아니라 tool_choice 안에 둡니다.
hljs json{
"tool_choice": {
"type": "auto",
"disable_parallel_tool_use": true
}
}
이 옵션은 message shape 규칙을 대신하지 않습니다. 단일 tool call에서도 matching result는 올바른 위치에 있어야 합니다.
production runner라면 assistant tool_use.id마다 matching result가 있는지, 여분의 tool_result가 없는지, 한 turn의 결과가 다음 user message에 모여 있는지, tool_result가 text보다 앞에 있는지, replayed history가 필요한 tool/thinking block을 삭제하지 않았는지 확인해야 합니다.
pending server tool이 있으면 다음 user message 규칙이 달라집니다
보통 모든 tool_result 뒤에 허용된 텍스트를 둘 수 있습니다. 하지만 같은 assistant turn에 해결되지 않은 server tool이 남아 있으면 다음 user message는 client가 담당하는 tool_result 만 포함해야 합니다. 뒤따르는 텍스트는 turn을 일찍 끝내 pending server tool을 지목하는 400을 만들 수 있습니다.
| assistant turn 상태 | 다음 user message에 허용되는 내용 |
|---|---|
| client tools만 있음 | matching results를 먼저, 그 뒤 허용된 text |
| client tools + pending server tool | matching client tool_result blocks만 |
| 미해결 client call 없음 | 나머지 contract를 따르는 일반 user message |
server tool을 닫으려고 가짜 client result를 만들지 마세요. server block을 보존하고 실제 실행한 client results만 돌려준 뒤 지원되는 server-tool loop가 계속되게 합니다.
긴 세션, 중단, 히스토리 압축이 재발을 만든다
긴 Claude Code 세션은 단순한 텍스트가 아니라 기계적 상태를 함께 갖고 있습니다. tool run을 중간에 멈췄거나, streaming response가 끊겼거나, IDE가 반쪽짜리 상태를 저장했거나, 사용자가 히스토리를 편집했거나, framework가 messages를 압축했다면 다음 request는 겉보기와 달리 구조적으로 깨져 있을 수 있습니다.
재발할 때는 “또 병렬로 불렀나”보다 “직전에 transcript가 어떻게 바뀌었나”를 보세요. 도구 실행을 멈췄는지, 오래된 transcript를 새 세션에 붙였는지, compaction이 tool result를 삭제했는지, tool set이 바뀌었는지, user message 앞쪽에 텍스트가 삽입됐는지 확인합니다.
| 상황 | 위험 | 안전한 회복 |
|---|---|---|
| tool run을 중간에 멈춤 | 반쪽짜리 tool state가 남음 | 그 turn 이전으로 rewind 후 작은 동작으로 검증 |
| history를 편집하거나 압축함 | 필요한 tool/thinking block이 빠짐 | human task summary로 시작하고 transcript는 복사하지 않기 |
| IDE만 실패함 | extension state 또는 bridge 문제 | CLI와 비교하고 version, 최소 실패 동작을 기록 |
| 앱이 stored messages를 replay함 | serializer가 순서나 ID를 바꿈 | API call 직전 exact message array 검증 |
“다시 시도해”, “천천히 해”, “한 번에 하나만 해”라는 프롬프트는 가끔 증상을 우회할 수 있지만 손상된 history를 고치지는 않습니다. 안정적인 해결은 손상된 turn 전으로 돌아가거나 API request가 tool result protocol을 다시 만족하게 만드는 것입니다.
다른 400과 다른 Claude 오류를 분리하세요
HTTP 400이라고 모두 같은 문제가 아닙니다. extra inputs, unsupported role, invalid system, malformed JSON, 모델 파라미터 오류는 request validation 문제일 수 있으며, tool/result history mismatch가 아닐 수 있습니다. 정확한 문구가 API Error: 400 due to tool use concurrency issues인지 먼저 보세요.
| 증상 | 더 맞는 분기 |
|---|---|
| extra inputs, unsupported role, invalid system, malformed JSON이 포함된 400 | request schema / parameter validation |
| tool call, edited history, long session 뒤의 400 | tool/result 또는 thinking-block mismatch |
| 429 또는 rate limit reached | Claude rate limit 전용 문서에서 allowance와 quota 확인 |
529 overloaded_error | Claude 529 전용 문서에서 capacity와 bounded retry 처리 |
| 500 또는 504 | server error, timeout, request_id, status check |
API Error: Connection error | Claude connection error 전용 문서에서 network, VPN, DNS, TLS, timeout 확인 |
| auth, billing, credits 문구 | account, project, organization, payment, credential |
Claude Status는 확인할 수 있지만 이 exact 400의 주된 증거는 아닙니다. 2026년 7월 15일 13:48(UTC+8) 확인에서 공식 summary는 Claude API와 Claude Code가 operational이고 active incident가 없다고 표시했습니다. 이것은 날짜가 붙은 분기 확인이며 현재 보장도, 로컬 IDE 상태나 messages 배열이 올바르다는 증명도 아닙니다. 여러 경로가 동시에 실패하면 live status를 다시 확인하세요.
issue나 support 전에 증거 패킷을 남기세요
좋은 증거는 비밀을 포함하지 않아도 됩니다. Claude Code version, IDE extension version, 실행 표면, 정확한 오류 문구, 시간과 timezone, 작업 종류, 일반 채팅 동작 여부, /rewind 또는 Esc 두 번의 결과, CLI와 IDE 비교, 직접 만든 클라이언트라면 민감정보를 제거한 message shape를 남기세요.
보내지 말아야 할 것도 분명합니다. API key, token, 개인 파일, 고객 데이터, 전체 기밀 transcript는 필요 없습니다. serializer 문제에서는 assistant turn의 tool_use ID, 다음 user message의 tool_result ID, 결과가 나뉘었는지, 누락됐는지, 텍스트 뒤에 놓였는지만으로도 충분히 진단할 수 있습니다.
같은 습관은 재발도 줄입니다. 오래된 tool turn을 손으로 삭제하지 마세요. tool_result 앞에 자연어 요약을 끼워 넣지 마세요. 한 assistant turn의 results를 여러 message로 나누지 마세요. status가 green이라고 request shape도 맞다고 판단하지 마세요. 새 세션으로 갈 때는 손상된 history가 아니라 사람이 읽을 수 있는 요약을 가져가세요.
FAQ
/rewind, Esc 두 번, /clear, 새 세션 중 무엇을 먼저 써야 하나요?
먼저 /rewind 또는 Esc 두 번입니다. 손상된 tool turn 이전으로 돌아가면서 유효한 작업 컨텍스트를 더 많이 보존합니다. /clear와 새 세션은 작은 검증 동작이 계속 실패할 때 사용하세요.
일반 채팅은 되는데 파일 도구만 실패하는 이유는 무엇인가요?
일반 채팅은 tool/result pairing이 필요하지 않습니다. 파일 읽기, 편집, bash, search는 tool state에 의존합니다. ID, 순서, block type이 깨지면 도구 동작만 실패할 수 있습니다.
정말 도구를 너무 많이 동시에 실행했다는 뜻인가요?
항상 그렇지는 않습니다. 실제 multi-tool turn 뒤에도 생길 수 있지만, 중단, 편집, history compaction, extension state, 누락된 result, 분리된 result, text before tool_result 뒤에도 생깁니다.
API key를 바꿔야 하나요?
이 exact error에서는 첫 조치가 아닙니다. 새 key는 malformed message history를 고치지 않습니다. error body나 account evidence가 auth branch를 가리킬 때만 credentials를 다룹니다.
parallel tool use를 끄면 해결되나요?
클라이언트가 여러 결과를 안전하게 모을 수 없거나 side effect가 있는 도구를 다룰 때만 검토하고, disable_parallel_tool_use: true는 tool_choice 안에 둡니다. 그래도 tool_result의 대응과 순서 규칙은 계속 지켜야 합니다.
도구가 실패하거나 skip됐을 때도 result가 필요한가요?
필요합니다. 해당 tool_use_id의 tool_result를 반환하고 is_error: true와 안전한 오류 설명을 넣으세요. 결과 집합의 완전성은 모든 작업이 성공했다는 뜻이 아니라 모든 call이 닫혔다는 뜻입니다.
results 뒤의 text가 server-tool 400을 만드는 이유는 무엇인가요?
일반적으로 text를 둘 수 있지만 같은 assistant turn에 pending server tool이 있으면 예외입니다. 다음 user message는 client tool_result blocks만 포함해야 합니다.
Claude Status도 확인해야 하나요?
여러 경로가 동시에 실패한다면 확인하세요. 하지만 exact 400이 그대로라면 중심은 status가 아니라 rewind와 message-shape repair입니다.
GitHub issue나 support ticket에는 무엇을 적어야 하나요?
version, surface, exact error text, timestamp, operation type, ordinary chat 상태, /rewind 결과, CLI versus IDE, sanitized message shape를 적으세요. key, token, 개인 파일, 전체 transcript는 보내지 마세요.
현재 세션을 언제 포기해야 하나요?
/rewind 후 같은 작은 tool action이 한두 번 더 같은 exact error를 내면 증거를 남기고 깨끗한 세션으로 이동하세요. 가져갈 것은 요약이지 손상된 tool history가 아닙니다.



